home *** CD-ROM | disk | FTP | other *** search
/ Collection of Tools & Utilities / Collection of Tools and Utilities.iso / keyb / recall12.zip / RECALL.ASM < prev    next >
Assembly Source File  |  1994-03-08  |  110KB  |  2,517 lines

  1. ;--------------------------------------------------------------------------;
  2. ;  Program:    Recall  .Asm                                                ;
  3. ;  Purpose:    Commandline editor and history TSR.                         ;
  4. ;  Notes:      Compiles under TURBO Assembler, v3.0. Requires DOS v2.xx    ;
  5. ;                 or higher. Editing keys are coded as PC extended scan    ;
  6. ;                 codes; otherwise, this uses only DOS calls.              ;
  7. ;              The overall design is derived from RDE (aka, Rainbow DOS    ;
  8. ;                 Editor) by Joe Kneidel. The methods used to install and  ;
  9. ;                 uninstall this TSR are from _MS-DOS Developer's Guide_,  ;
  10. ;                 by Angermayer and Jaeger.                                ;
  11. ;  Status:     Released into the >>>public domain<<<. Enjoy! If you use    ;
  12. ;                 it, let me know what you think. You don't have to send   ;
  13. ;                 any money, just comments and suggestions.                ;
  14. ;  Updates:    24-Oct-90, v1.0a, GAT                                       ;
  15. ;                 - initial version                                        ;
  16. ;              28-Oct-90, v1.0b, GAT                                       ;
  17. ;                 - renamed get_LineFromUser to get_CmdLine and            ;
  18. ;                   add_LineToBuffer to store_CmdInBuf.                    ;
  19. ;                 - made sure to zero out CH in add_LineToBuffer.          ;
  20. ;                 - excluded CR from byte count in get_CmdLine.            ;
  21. ;                 - kept track of CurCmd rather than PrevCmd/NextCmd and   ;
  22. ;                   moved checks on command from recall_CmdFromBuf to      ;
  23. ;                   mov_pcmd and mov_ncmd.                                 ;
  24. ;                 - specified command table as an array of structures and  ;
  25. ;                   revised ways it was accessed in get_CmdLine.           ;
  26. ;                 - rearranged various procedures.                         ;
  27. ;                 - spruced up comments.                                   ;
  28. ;              31-Oct-90, v1.1a, GAT                                       ;
  29. ;                 - removed notices about preliminary notices.             ;
  30. ;                 - cleanup up help message a bit.                         ;
  31. ;                 - avoided use of LABELs.                                 ;
  32. ;                 - added list_CmdLines to list recall buffer contents.    ;
  33. ;              10-Nov-91, v1.2a, GAT                                       ;
  34. ;                 - caught and fixed bug involving DOS input redirection   ;
  35. ;                   which caused 0Ah characters to remain in commandline.  ;
  36. ;                 - revised include file names.                            ;
  37. ;                 - added pseudo-environment so program name will show up  ;
  38. ;                   with things like PMAP, MANIFEST, and MEM.              ;
  39. ;                 - uses INT 2D as per Ralf Brown's Alternate Multiplex    ;
  40. ;                   Interrupt proposal.                                    ;
  41. ;                 - shares interrupts as per IBM's Interrupt Sharing       ;
  42. ;                   Protocol.                                              ;
  43. ;              16-Nov-91, GAT                                              ;
  44. ;                 - made minor changes in return values from the Int 2d    ;
  45. ;                   handler to track Ralf's proposal.                      ;
  46. ;              08-Jan-92, GAT                                              ;
  47. ;                 - bumped up verion after catching bug in procedure to    ;
  48. ;                   check if installed.                                    ;
  49. ;              03-Jul-93, v1.2c, GAT                                       ;
  50. ;                 - compiled with TASM v3.0.                               ;
  51. ;                 - version number now comes from makefile.                ;
  52. ;                 - specified ??date in lowercase.                         ;
  53. ;--------------------------------------------------------------------------;
  54.  
  55. ;--------------------------------------------------------------------------;
  56. ;  Author:     George A. Theall                                            ;
  57. ;  SnailMail:  TifaWARE                                                    ;
  58. ;              610 South 48th St                                           ;
  59. ;              Philadelphia, PA.  19143                                    ;
  60. ;              U.S.A.                                                      ;
  61. ;  E-Mail:     george@tifaware.com                                         ;
  62. ;              theall@popmail.tju.edu                                      ;
  63. ;              theall@mcneil.sas.upenn.edu                                 ;
  64. ;              george.theall@satalink.com                                  ;
  65. ;--------------------------------------------------------------------------;
  66.  
  67. %NEWPAGE
  68. ;--------------------------------------------------------------------------;
  69. ;                          D I R E C T I V E S                             ;
  70. ;--------------------------------------------------------------------------;
  71. DOSSEG
  72. MODEL     tiny
  73.  
  74. IDEAL
  75. LOCALS
  76. JUMPS
  77.  
  78. FALSE               EQU       0
  79. TRUE                EQU       NOT FALSE
  80. BELL                EQU       7
  81. BS                  EQU       8
  82. TAB                 EQU       9
  83. CR                  EQU       13
  84. LF                  EQU       10
  85. ESCAPE              EQU       27             ; nb: ESC is a TASM keyword
  86. SPACE               EQU       ' '
  87. KEY_F1              EQU       3bh
  88. KEY_F2              EQU       3ch
  89. KEY_F3              EQU       3dh
  90. KEY_F4              EQU       3eh
  91. KEY_F5              EQU       3fh
  92. KEY_F6              EQU       40h
  93. KEY_F7              EQU       41h
  94. KEY_F8              EQU       42h
  95. KEY_F9              EQU       43h
  96. KEY_F10             EQU       44h
  97. KEY_HOME            EQU       47h
  98. KEY_UP              EQU       48h
  99. KEY_PGUP            EQU       49h
  100. KEY_LEFT            EQU       4bh
  101. KEY_RIGHT           EQU       4dh
  102. KEY_END             EQU       4fh
  103. KEY_DOWN            EQU       50h
  104. KEY_PGDN            EQU       51h
  105. KEY_INS             EQU       52h
  106. KEY_DEL             EQU       53h
  107. KEY_C_F1            EQU       5eh
  108. KEY_C_F2            EQU       5fh
  109. KEY_C_F3            EQU       60h
  110. KEY_C_F4            EQU       61h
  111. KEY_C_F5            EQU       62h
  112. KEY_C_F6            EQU       63h
  113. KEY_C_F7            EQU       64h
  114. KEY_C_F8            EQU       65h
  115. KEY_C_F9            EQU       66h
  116. KEY_C_F10           EQU       67h
  117. KEY_C_LEFT          EQU       73h
  118. KEY_C_RIGHT         EQU       74h
  119. KEY_C_END           EQU       75h
  120. KEY_C_PGDN          EQU       76h
  121. KEY_C_HOME          EQU       77h
  122. KEY_C_PGUP          EQU       84h
  123. KEY_F11             EQU       85h
  124. KEY_F12             EQU       86h
  125. KEY_C_F11           EQU       89h
  126. KEY_C_F12           EQU       8ah
  127. @16BIT              EQU       (@Cpu AND 8) EQ 0
  128. @32BIT              EQU       (@Cpu AND 8)
  129. NOWARN RES
  130. MACRO    PUSHA                               ;; Pushs all registers
  131.    IF @Cpu AND 2                             ;;  if for 80186 or better
  132.       pusha                                  ;;   use regular opcode
  133.    ELSE                                      ;;  else
  134.       push ax cx dx bx sp bp si di           ;;   nb: order matters!
  135.                                              ;;   nb: SP is not original!
  136.    ENDIF
  137. ENDM
  138. MACRO    POPA                                ;; Pops all registers
  139.    IF @Cpu AND 2                             ;;  if for 80186 or better
  140.       popa                                   ;;   use regular opcode
  141.    ELSE                                      ;;  else
  142.       pop di si bp bx bx dx cx ax            ;;   nb: order matters!
  143.                                              ;;   nb: don't pop SP!
  144.    ENDIF
  145. ENDM
  146. NOWARN RES
  147. MACRO    ZERO     RegList                    ;; Zeros registers
  148.    IRP      Reg, <RegList>
  149.          xor      Reg, Reg
  150.    ENDM
  151. ENDM
  152.  
  153. DOS                 EQU       21h            ; main MSDOS interrupt
  154. STDIN               EQU       0              ; standard input
  155. STDOUT              EQU       1              ; standard output
  156. STDERR              EQU       2              ; error output
  157. STDAUX              EQU       3              ; COM port
  158. STDPRN              EQU       4              ; printer
  159. TSRMAGIC            EQU       424bh          ; magic number
  160. STRUC     ISR
  161.           Entry     DW        10EBh          ; short jump ahead 16 bytes
  162.           OldISR    DD        ?              ; next ISR in chain
  163.           Sig       DW        TSRMAGIC       ; magic number
  164.           EOIFlag   DB        ?              ; 0 (80) if soft(hard)ware int
  165.           Reset     DW        ?              ; short jump to hardware reset
  166.           Reserved  DB        7 dup (0)
  167. ENDS
  168. STRUC     ISRHOOK
  169.           Vector    DB        ?              ; vector hooked
  170.           Entry     DW        ?              ; offset of TSR entry point
  171. ENDS
  172. STRUC     TSRSIG
  173.           Company   DB        8 dup (" ")    ; blank-padded company name
  174.           Product   DB        8 dup (" ")    ; blank-padded product name
  175.           Desc      DB        64 dup (0)     ; ASCIIZ product description
  176. ENDS
  177. GLOBAL at : PROC
  178. GLOBAL errmsg : PROC
  179.    GLOBAL ProgName : BYTE                    ; needed for errmsg()
  180.    GLOBAL EOL : BYTE                         ; ditto
  181. GLOBAL fgetc : PROC
  182. GLOBAL fputc : PROC
  183. GLOBAL fputs : PROC
  184. GLOBAL getchar : PROC
  185. GLOBAL getdate : PROC
  186. GLOBAL getswtch : PROC
  187. GLOBAL gettime : PROC
  188. GLOBAL getvdos : PROC
  189. GLOBAL getvect : PROC
  190. GLOBAL isatty : PROC
  191. GLOBAL kbhit : PROC
  192. GLOBAL pause : PROC
  193. GLOBAL putchar : PROC
  194. GLOBAL setvect : PROC
  195. GLOBAL sleep : PROC
  196. GLOBAL find_NextISR : PROC
  197. GLOBAL find_PrevISR : PROC
  198. GLOBAL hook_ISR : PROC
  199. GLOBAL unhook_ISR : PROC
  200. GLOBAL free_Env : PROC
  201. GLOBAL fake_Env : PROC
  202. GLOBAL check_ifInstalled : PROC
  203. GLOBAL install_TSR : PROC
  204. GLOBAL remove_TSR : PROC
  205.  
  206. GLOBAL atoi : PROC
  207. GLOBAL atou : PROC
  208. GLOBAL utoa : PROC
  209.  
  210. EOS                 EQU       0              ; terminates strings
  211. GLOBAL isalpha : PROC
  212. GLOBAL isdigit : PROC
  213. GLOBAL islower : PROC
  214. GLOBAL isupper : PROC
  215. GLOBAL iswhite : PROC
  216. GLOBAL memcmp : PROC
  217. GLOBAL strchr : PROC
  218. GLOBAL strcmp : PROC
  219. GLOBAL strlen : PROC
  220. GLOBAL tolower : PROC
  221. GLOBAL toupper : PROC
  222.  
  223.  
  224.  
  225.  
  226. ; BUFSIZE specifies size of the recall buffer for collecting commandlines.
  227. ; Values of 255 or below are risky because that's the maximum buffer size
  228. ; for subfunction 10 of Int 21h and my code in add_LineToBuffer does not 
  229. ; make sure commandlines will fit. I foresee no problems, however, with 
  230. ; larger values up to about 60K.
  231. BUFSIZE   equ       1024                     ; >>>CHANGE AT YOUR RISK<<<
  232. ERRH      equ       1                        ; errorlevel if help given
  233. ERRINS    equ       10                       ; errorlevel if install failed
  234. ERRUNI    equ       20                       ; errorlevel if uninstall failed
  235. ERRNYI    equ       25                       ; errorlevel if not yet installed
  236. OFF       equ       0
  237. ON        equ       1
  238.  
  239.  
  240. %NEWPAGE
  241. ;--------------------------------------------------------------------------;
  242. ;                        C O D E    S E G M E N T                          ;
  243. ;--------------------------------------------------------------------------;
  244. CODESEG
  245.  
  246. ORG       0                                  ; address of code segment start
  247. SegStart  DB        ?                        ;    used in when installing
  248.  
  249. ORG       80h                                ; address of commandline
  250. CmdLen    DB        ?
  251. CmdLine   DB        127 DUP (?)
  252.  
  253. ORG       100h                               ; start of .COM file
  254. STARTUPCODE
  255.           jmp       main
  256.  
  257.  
  258. %NEWPAGE
  259. ;--------------------------------------------------------------------------;
  260. ;                        R E S I D E N T   D A T A                         ;
  261. ;--------------------------------------------------------------------------;
  262. TSR_Sig   TSRSIG    <'TifaWARE', 'RECALL  ',\
  263.                     'commandline editor and history TSR'>
  264. TSR_Ver   DW        (2 SHL 8) + 1            ; (minor shl 8) + major
  265. MPlex     DB        ?                        ; multiplex ID
  266. HookTbl   ISRHOOK   <21h, do_Int21>
  267.           ISRHOOK   <2dh, do_Int2D>          ; 2d must be last!!!
  268.  
  269. OldAX     DW        ?                        ; value of AX register when my
  270.                                              ;    handler is first called
  271. OldStack  DD        ?                        ; address of caller's stack
  272. CurCmd    DW        0                        ; pointer to current command
  273.                                              ;    in recall buffer
  274. InsMode   DB        ON                       ; InsertMode toggle flag
  275.  
  276. STRUC     CMD                                ; structure for editing cmd
  277.           Key       DB        ?              ;    extended code for key
  278.           Function  DW        ?              ;    address of editing function
  279. ENDS
  280. CmdTbl    CMD       <KEY_LEFT,     OFFSET mov_lchar>
  281.           CMD       <KEY_RIGHT,    OFFSET mov_rchar>
  282.           CMD       <KEY_PGUP,     OFFSET mov_lword>
  283.           CMD       <KEY_PGDN,     OFFSET mov_rword>
  284.           CMD       <KEY_HOME,     OFFSET mov_bol>
  285.           CMD       <KEY_END,      OFFSET mov_eol>
  286.           CMD       <KEY_UP,       OFFSET mov_pcmd>
  287.           CMD       <KEY_DOWN,     OFFSET mov_ncmd>
  288.           CMD       <BS,           OFFSET del_lchar>
  289.           CMD       <KEY_C_LEFT,   OFFSET del_lchar>
  290.           CMD       <KEY_DEL,      OFFSET del_rchar>
  291.           CMD       <KEY_C_RIGHT,  OFFSET del_rchar>
  292.           CMD       <KEY_C_PGUP,   OFFSET del_lword>
  293.           CMD       <KEY_C_PGDN,   OFFSET del_rword>
  294.           CMD       <KEY_C_HOME,   OFFSET del_bol>
  295.           CMD       <KEY_C_END,    OFFSET del_eol>
  296.           CMD       <ESCAPE,       OFFSET del_line>
  297.           CMD       <KEY_C_F9,     OFFSET del_buf>
  298.           CMD       <KEY_INS,      OFFSET toggle_InsMode>
  299.           CMD       <0,            OFFSET ring_Bell>   ; >>>must be last<<<
  300.  
  301.  
  302. %NEWPAGE
  303. ;--------------------------------------------------------------------------;
  304. ;                          L O C A L   S T A C K                           ;
  305. ;--------------------------------------------------------------------------;
  306.           DB        16 dup("STACK   ")       ; 128 bytes for local stack
  307. StackTop  =         $
  308.  
  309.  
  310. %NEWPAGE
  311. ;--------------------------------------------------------------------------;
  312. ;                        R E S I D E N T   C O D E                         ;
  313. ;--------------------------------------------------------------------------;
  314. ;----  is_CharWhite  ------------------------------------------------------;
  315. ;  Purpose:    Tests if character is either a blank or a tab.              ;
  316. ;  Notes:      none                                                        ;
  317. ;  Entry:      AL = character to be tested.                                ;
  318. ;  Exit:       Zero flag set if true, cleared otherwise.                   ;
  319. ;  Calls:      none                                                        ;
  320. ;  Changes:    flags                                                       ;
  321. ;--------------------------------------------------------------------------;
  322. PROC is_CharWhite
  323.  
  324.           cmp       al, SPACE                ; if == SPACE then zf = 1
  325.           jz        SHORT @@Fin
  326.           cmp       al, TAB                  ; if == TAB then zf = 1
  327. @@Fin:
  328.           ret
  329. ENDP is_CharWhite
  330.  
  331.  
  332. ;----  get_KeyNoEcho  -----------------------------------------------------;
  333. ;  Purpose:    Reads key from STDIN, waiting as necessary.                 ;
  334. ;  Notes:      Allows DESQview to operate efficiently if task inactive.    ;
  335. ;              Ctrl-C and Ctrl-Break generate Int 23h.                     ;
  336. ;  Entry:      n/a                                                         ;
  337. ;  Exit:       AL = character (0 => extended code available next).         ;
  338. ;  Calls:      none                                                        ;
  339. ;  Changes:    AX                                                          ;
  340. ;--------------------------------------------------------------------------;
  341. PROC get_KeyNoEcho
  342.  
  343.           mov       ah, 8
  344.           int       DOS
  345.           ret
  346. ENDP get_KeyNoEcho
  347.  
  348.  
  349. ;----  ring_Bell  ---------------------------------------------------------;
  350. ;  Purpose:    Rings the console bell as a warning to user.                ;
  351. ;  Notes:      none                                                        ;
  352. ;  Entry:      n/a                                                         ;
  353. ;  Exit:       n/a                                                         ;
  354. ;  Calls:      none                                                        ;
  355. ;  Changes:    none                                                        ;
  356. ;--------------------------------------------------------------------------;
  357. PROC ring_Bell
  358.  
  359.           push      ax dx
  360.           mov       ah, 2
  361.           mov       dl, BELL
  362.           int       DOS
  363.           pop       dx ax
  364.           ret
  365. ENDP ring_Bell
  366.  
  367.  
  368. ;----  display_Char  ------------------------------------------------------;
  369. ;  Purpose:    Displays character on STDOUT and advances to next char.     ;
  370. ;  Notes:      Do *not* call this procedure to display backspaces; use     ;
  371. ;                   backup_Cursor instead. Though they'd be displayed      ;
  372. ;                   properly, BX would be *incremented* here.              ;
  373. ;              Does *not* adjust CH or CL.                                 ;
  374. ;  Entry:      AL = character to display,                                  ;
  375. ;              BX = pointer to current position in commandline.            ;
  376. ;  Exit:       BX++                                                        ;
  377. ;  Calls:      none                                                        ;
  378. ;  Changes:    BX                                                          ;
  379. ;--------------------------------------------------------------------------;
  380. PROC display_Char
  381.  
  382.           push      ax dx
  383.           mov       dl, al
  384.           mov       ah, 2
  385.           int       DOS
  386.           inc       bx                       ; move to next char on cmdline
  387.           pop       dx ax
  388.           ret
  389. ENDP display_Char
  390.  
  391.  
  392. ;----  advance_Cursor  ----------------------------------------------------;
  393. ;  Purpose:    Moves the cursor forwards on the screen.                    ;
  394. ;  Notes:      none                                                        ;
  395. ;  Entry:      BX = pointer to current position in commandline,            ;
  396. ;              CH = # of bytes to end of line,                             ;
  397. ;              SI = # of bytes to advance.                                 ;
  398. ;  Exit:       BX += SI,                                                   ;
  399. ;              CH -= SI.                                                   ;
  400. ;  Calls:      display_Char                                                ;
  401. ;  Changes:    AX, CH,                                                     ;
  402. ;              BX (display_Char)                                           ;
  403. ;--------------------------------------------------------------------------;
  404. PROC advance_Cursor
  405.  
  406.           or        si, si                   ; anything to skip over?
  407.           jz        SHORT @@Fin
  408.  
  409. ; Adjust CH now. (This could be left until later - no big deal.)
  410.           mov       al, ch
  411.           ZERO      ah
  412.           sub       ax, si
  413.           mov       ch, al                   ; CH -= SI
  414.  
  415. ; Display SI characters on commandline.
  416.           push      cx
  417.           mov       cx, si
  418. @@NextChar:
  419.           mov       al, [bx]
  420.           call      display_Char             ; nb: increments BX too
  421.           loop      SHORT @@NextChar
  422.           pop       cx
  423.  
  424. @@Fin:
  425.           ret
  426. ENDP advance_Cursor
  427.  
  428.  
  429. ;----  backup_Cursor  -----------------------------------------------------;
  430. ;  Purpose:    Moves the cursor backwards on the screen.                   ;
  431. ;  Notes:      Does *not* handle properly line-wrapping yet.               ;
  432. ;  Entry:      BX = pointer to current position in commandline,            ;
  433. ;              CH = # of bytes to end of line,                             ;
  434. ;              SI = # of bytes to back up.                                 ;
  435. ;  Exit:       BX -= SI,                                                   ;
  436. ;              CH += SI.                                                   ;
  437. ;  Calls:      none                                                        ;
  438. ;  Changes:    AX, BX, CH                                                  ;
  439. ;--------------------------------------------------------------------------;
  440. PROC backup_Cursor
  441.  
  442.           or        si, si                   ; anything to skip over?
  443.           jz        SHORT @@Fin
  444.  
  445. ; Adjust BX and CH now. (This could be left until later - no big deal.)
  446.           sub       bx, si                   ; BX -= SI
  447.           mov       al, ch
  448.           ZERO      ah
  449.           add       ax, si
  450.           mov       ch, al                   ; CH += SI
  451.  
  452. ; Back up cursor by displaying non-destructive backspaces.
  453.           push      cx dx
  454.           mov       ah, 2
  455.           mov       cx, si
  456.           mov       dl, BS
  457. @@PrevChar:
  458.           int       DOS
  459.           loop      SHORT @@PrevChar
  460.           pop       dx cx
  461.  
  462. @@Fin:
  463.           ret
  464. ENDP backup_Cursor
  465.  
  466.  
  467. ;----  delete_Chars  ------------------------------------------------------;
  468. ;  Purpose:    Deletes characters from commandline.                        ;
  469. ;  Notes:      No checks are done on SI's validity; ie, caller should      ;
  470. ;                   ensure there are enough characters on line to delete.  ;
  471. ;  Entry:      BX = pointer to current position in commandline,            ;
  472. ;              CH = # of bytes to end of line,                             ;
  473. ;              CL = # of bytes left in commandline,                        ;
  474. ;              SI = # of characters to delete.                             ;
  475. ;  Exit:       CH -= SI,                                                   ;
  476. ;              CL += SI.                                                   ;
  477. ;  Calls:      display_Char, backup_Cursor                                 ;
  478. ;  Changes:    CH, CL, SI,                                                 ;
  479. ;              AX (backup_Cursor)                                          ;
  480. ;--------------------------------------------------------------------------;
  481. PROC delete_Chars
  482.  
  483.           or        si, si                   ; anything to delete?
  484.           jz        SHORT @@Fin
  485.  
  486. ; Adjust CH and CL now while I have SI handy. At the same time
  487. ; I am also computing the number of characters to shift.
  488.           mov       al, cl
  489.           ZERO      ah
  490.           add       ax, si
  491.           mov       cl, al                   ; CL += SI
  492.           mov       al, ch
  493.           sub       ax, si
  494.           mov       ch, al                   ; CH -= SI
  495.           push      cx                       ; final values of CH and CL
  496.           push      ax                       ; used to back up cursor
  497.  
  498. ; Shift CH - SI characters remaining on line to the left by SI characters.
  499.           mov       cx, ax                   ; CX = CH - SI
  500.           jcxz      SHORT @@CoverUp          ; skip if deleting to eol
  501. @@NextChar:
  502.           mov       al, [bx+si]
  503.           mov       [bx], al
  504.           call      display_Char             ; nb: increments BX too
  505.           loop      SHORT @@NextChar
  506.  
  507. ; Display spaces to overwrite chars remaining on line.
  508. @@CoverUp:
  509.           mov       al, SPACE
  510.           mov       cx, si
  511. @@NextBlank:
  512.           call      display_Char             ; nb: increments BX too
  513.           loop      SHORT @@NextBlank
  514.  
  515. ; Back up cursor to its location on invocation.
  516.           pop       ax                       ; CH - SI
  517.           add       si, ax                   ; SI += (CH - SI)
  518.           call      backup_Cursor            ; nb: decrements BX too
  519.           pop       cx
  520.  
  521. ; NB: BX will be incremented (CH - SI) + SI times by display_Char and
  522. ; decremented CH times by backup_Cursor so overall it won't change.
  523. @@Fin:
  524.           ret
  525. ENDP delete_Chars
  526.  
  527.  
  528. ;----  add_CharToLine  ----------------------------------------------------;
  529. ;  Purpose:    Adds a character to commandline buffer.                     ;
  530. ;  Notes:      Checks to see if buffer would overflow first.               ;
  531. ;  Entry:      AL = character to add,                                      ;
  532. ;              BX = pointer to current position in line,                   ;
  533. ;              CH = number of characters until end of line,                ;
  534. ;              CL = number of bytes left in line.                          ;
  535. ;  Exit:       BX and CX changed as appropriate.                           ;
  536. ;  Calls:      display_Char, backup_Cursor, ring_Bell                      ;
  537. ;  Changes:    AX, BX, CX                                                  ;
  538. ;--------------------------------------------------------------------------;
  539. PROC add_CharToLine
  540.  
  541. ; Check for space unless insert mode is OFF *and* not at eol.
  542.           cmp       [cs:InsMode], ON
  543.           je        SHORT @@CheckForSpace
  544.           or        ch, ch
  545.           jz        SHORT @@CheckForSpace
  546.  
  547. ; Overwrite existing character while in not at eol.
  548.           mov       [bx], al
  549.           call      display_Char             ; nb: increments BX too
  550.           dec       ch
  551.           jmp       SHORT @@Fin
  552.  
  553. @@CheckForSpace:
  554.           or        cl, cl
  555.           jz        SHORT @@Abort
  556.           or        ch, ch
  557.           jnz       SHORT @@AddWithShift
  558.  
  559. ; At end of line.
  560.           mov       [bx], al
  561.           call      display_Char             ; nb: increments BX too
  562.           dec       cl
  563.           jmp       SHORT @@Fin
  564.  
  565. ; Add character and shift everything to right over by 1 position.
  566. @@AddWithShift:
  567.           push      cx dx
  568.           mov       cl, ch                   ; CH = chars to eol
  569.           ZERO      ch
  570.           mov       si, cx                   ; save for backing up
  571.           inc       cl                       ; but add 1 to display new char
  572. @@NextChar:
  573.           mov       dl, [bx]                 ; use DL as temporary storage
  574.           mov       [bx], al
  575.           call      display_Char             ; nb: increments BX too
  576.           mov       al, dl                   ; recover previous char
  577.           loop      SHORT @@NextChar
  578.           call      backup_Cursor            ; nb: decrements BX too
  579.           pop       dx cx
  580.           dec       cl
  581.           jmp       SHORT @@Fin
  582.  
  583. @@Abort:
  584.           call      ring_Bell                ; if out of space
  585.  
  586. @@Fin:
  587.           ret
  588. ENDP add_CharToLine
  589.  
  590.  
  591. ;----  find_StartofPrevWord  ----------------------------------------------;
  592. ;  Purpose:    Locates start of previous word in commandline.              ;
  593. ;  Notes:      "Words" are delineated by blanks and/or start/finish of     ;
  594. ;                   the commandline.                                       ;
  595. ;  Entry:      BX = pointer to current position in commandline.            ;
  596. ;  Exit:       SI = # of characters from BX to start of previous word.     ;
  597. ;  Calls:      is_CharWhite                                                ;
  598. ;  Changes:    AX, SI                                                      ;
  599. ;--------------------------------------------------------------------------;
  600. PROC find_StartofPrevWord
  601.  
  602.           push      bx dx
  603.           inc       dx
  604.           inc       dx                       ; DX now points to bol
  605.           mov       si, bx                   ; SI = current position
  606.  
  607. ; Skip over any whitespace. Note: don't bother with 1st character -
  608. ; think of how it should behave if positioned at start of a word.
  609. @@SkipWhite:
  610.           dec       bx
  611.           cmp       bx, dx
  612.           jb        SHORT @@Fin              ; done if BX < bol
  613.           mov       al, [bx]
  614.           call      is_CharWhite
  615.           jz        SHORT @@SkipWhite
  616.  
  617. ; Next skip over non-blanks until the start of the word.
  618. @@SkipWord:
  619.           dec       bx
  620.           cmp       bx, dx
  621.           jb        SHORT @@Fin              ; done if BX < bol
  622.           mov       al, [bx]
  623.           call      is_CharWhite
  624.           jnz       SHORT @@SkipWord
  625.  
  626. ; Finally compute how many characters must be skipped.
  627. @@Fin:
  628.           inc       bx                       ; backed up 1 too many
  629.           sub       si, bx
  630.           pop       dx bx
  631.           ret
  632. ENDP find_StartofPrevWord
  633.  
  634.  
  635. ;----  find_StartofNextWord  ----------------------------------------------;
  636. ;  Purpose:    Locates start of next word in commandline.                  ;
  637. ;  Notes:      "Words" are delineated by blanks and/or start/finish of     ;
  638. ;                   the commandline.                                       ;
  639. ;  Entry:      BX = pointer to current position in commandline.            ;
  640. ;  Exit:       SI = # of characters from BX to start of next word.         ;
  641. ;  Calls:      is_CharWhite                                                ;
  642. ;  Changes:    AX, SI                                                      ;
  643. ;--------------------------------------------------------------------------;
  644. PROC find_StartofNextWord
  645.  
  646.           push      bx dx
  647.           mov       dx, bx
  648.           mov       al, ch
  649.           ZERO      ah
  650.           add       dx, ax                   ; DX now points to eol
  651.  
  652. ; Skip over any existing word. Note: unlike find_StartofPrevWord, here
  653. ; we do not want to initially skip ahead - imagine if cursor were at
  654. ; a blank before the start of a word.
  655. @@SkipWord:
  656.           cmp       bx, dx
  657.           je        SHORT @@Fin              ; done if BX = eol
  658.           mov       al, [bx]
  659.           inc       bx
  660.           call      is_CharWhite
  661.           jnz       SHORT @@SkipWord
  662.  
  663. ; Next skip over whitespace until the start of the word.
  664. @@SkipWhite:
  665.           cmp       bx, dx
  666.           je        SHORT @@Fin              ; done if BX = eol
  667.           mov       al, [bx]
  668.           inc       bx
  669.           call      is_CharWhite
  670.           jz        SHORT @@SkipWhite
  671.           dec       bx                       ; point back to white space
  672.  
  673. ; Finally compute how many characters must be skipped.
  674. @@Fin:
  675.           mov       si, bx                   ; where we are now
  676.           pop       dx bx
  677.           sub       si, bx                   ; less where we started from
  678.           ret
  679. ENDP find_StartofNextWord
  680.  
  681.  
  682. ;----  recall_CmdFromBuf  -------------------------------------------------;
  683. ;  Purpose:    Replaces current with a commandline from buffer.            ;
  684. ;  Notes:      Does *not* check SI's validity.                             ;
  685. ;  Entry:      BX = pointer to current position in commandline,            ;
  686. ;              CH = # of bytes to end of line,                             ;
  687. ;              CL = # of bytes left in commandline,                        ;
  688. ;              SI = pointer to command in recall buffer.                   ;
  689. ;  Entry:      BX = pointer to end of new commandline,                     ;
  690. ;              CH = 0,                                                     ;
  691. ;              CL = # of bytes left in new commandline.                    ;
  692. ;  Calls:      del_line, display_Char, ring_Bell                           ;
  693. ;  Changes:    AL, BX, CH, CL, SI                                          ;
  694. ;--------------------------------------------------------------------------;
  695. PROC recall_CmdFromBuf
  696.  
  697. ; Clear current commandline and display new one.
  698.           push      si                       ; since del_line zaps SI
  699.           call      del_line                 ; changes CH and CL such that CX
  700.                                              ;   == max # of chars in buffer
  701.           pop       si
  702. @@NextChar:
  703.           mov       al, [cs:si]
  704.           cmp       al, CR
  705.           je        SHORT @@Fin
  706.           inc       si
  707.           mov       [bx], al
  708.           call      display_Char             ; nb: increments BX too
  709.           loop      SHORT @@NextChar         ; continue as long as CX > 0
  710.           cmp       [BYTE cs:si], CR         ; did loop end prematurely?
  711.           je        SHORT @@Fin              ;   no
  712.           call      ring_Bell                ;   yes, warn user
  713.  
  714. @@Fin:
  715.           ret
  716. ENDP recall_CmdFromBuf
  717.  
  718.  
  719. ;----  mov_lchar  ---------------------------------------------------------;
  720. ;  Purpose:    Moves cursor left in the commandline.                       ;
  721. ;  Notes:      none                                                        ;
  722. ;  Entry:      BX = pointer to current position in commandline,            ;
  723. ;              CH = # of bytes to end of line.                             ;
  724. ;  Exit:       BX--,                                                       ;
  725. ;              CH++,                                                       ;
  726. ;              SI = 1 (or 0 if already at bol).                            ;
  727. ;  Calls:      backup_Cursor                                               ;
  728. ;  Changes:    SI,                                                         ;
  729. ;              BX, CH (backup_Cursor)                                      ;
  730. ;--------------------------------------------------------------------------;
  731. PROC mov_lchar
  732.  
  733.           mov       si, bx
  734.           sub       si, dx
  735.           cmp       si, 2
  736.           ja        SHORT @@MoveIt           ; at bol if BX - DX <= 2
  737.           ZERO      si
  738.           jmp       SHORT @@Fin
  739.  
  740. @@MoveIt:
  741.           mov       si, 1                    ; move 1 character
  742.           call      backup_Cursor            ; nb: decrements BX too
  743.  
  744. @@Fin:
  745.           ret
  746. ENDP mov_lchar
  747.  
  748.  
  749. ;----  mov_rchar  ---------------------------------------------------------;
  750. ;  Purpose:    Moves cursor right in the commandline.                      ;
  751. ;  Notes:      none                                                        ;
  752. ;  Entry:      BX = pointer to current position in commandline,            ;
  753. ;              CH = # of bytes to end of line.                             ;
  754. ;  Exit:       BX++,                                                       ;
  755. ;              CH--,                                                       ;
  756. ;              SI = 1 (or 0 if already at eol).                            ;
  757. ;  Calls:      advance_Cursor                                              ;
  758. ;  Changes:    SI,                                                         ;
  759. ;              AX, BX, CH (advance_Cursor)                                 ;
  760. ;--------------------------------------------------------------------------;
  761. PROC mov_rchar
  762.  
  763.           ZERO      si                       ; set SI = 0 first
  764.           or        ch, ch
  765.           jz        SHORT @@Fin              ; abort if CH = 0
  766.           inc       si                       ; move 1 character
  767.           call      advance_Cursor           ; nb: increments BX
  768.  
  769. @@Fin:
  770.           ret
  771. ENDP mov_rchar
  772.  
  773.  
  774. ;----  mov_lword  ---------------------------------------------------------;
  775. ;  Purpose:    Moves cursor to start of previous word.                     ;
  776. ;  Notes:      none                                                        ;
  777. ;  Entry:      BX = pointer to current position in commandline,            ;
  778. ;              CH = # of bytes to end of line.                             ;
  779. ;  Exit:       BX and CH adjusted as appropriate.                          ;
  780. ;  Calls:      find_StartofPrevWord, backup_Cursor                         ;
  781. ;  Changes:    SI, (find_StartofPrevWord)                                  ;
  782. ;              AX, BX, CH (backup_Cursor)                                  ;
  783. ;--------------------------------------------------------------------------;
  784. PROC mov_lword
  785.  
  786.           call      find_StartofPrevWord
  787.           call      backup_Cursor
  788.           ret
  789. ENDP mov_lword
  790.  
  791.  
  792. ;----  mov_rword  ---------------------------------------------------------;
  793. ;  Purpose:    Moves cursor to start of next word.                         ;
  794. ;  Notes:      none                                                        ;
  795. ;  Entry:      BX = pointer to current position in commandline,            ;
  796. ;              CH = # of bytes to end of line.                             ;
  797. ;  Exit:       BX and CH adjusted as appropriate.                          ;
  798. ;  Calls:      find_StartofNextWord, advance_Cursor                        ;
  799. ;  Changes:    SI, (find_StartofNextWord)                                  ;
  800. ;              AX, BX, CH (advance_Cursor)                                 ;
  801. ;--------------------------------------------------------------------------;
  802. PROC mov_rword
  803.  
  804.           call      find_StartofNextWord
  805.           call      advance_Cursor
  806.           ret
  807. ENDP mov_rword
  808.  
  809.  
  810. ;----  mov_bol  -----------------------------------------------------------;
  811. ;  Purpose:    Moves cursor to start of commandline.                       ;
  812. ;  Notes:      none                                                        ;
  813. ;  Entry:      BX = pointer to current position in commandline,            ;
  814. ;              CH = # of bytes to end of line.                             ;
  815. ;  Exit:       BX = DX + 2,                                                ;
  816. ;              CH = # of characters in commandline,                        ;
  817. ;              SI = # of characters backed up.                             ;
  818. ;  Calls:      backup_Cursor                                               ;
  819. ;  Changes:    SI,                                                         ;
  820. ;              AX, BX, CH (backup_Cursor)                                  ;
  821. ;--------------------------------------------------------------------------;
  822. PROC mov_bol
  823.  
  824.           mov       si, bx
  825.           sub       si, dx
  826.           dec       si
  827.           dec       si                       ; SI = BX - (DX + 2)
  828.           call      backup_Cursor
  829.           ret
  830. ENDP mov_bol
  831.  
  832.  
  833. ;----  mov_eol  -----------------------------------------------------------;
  834. ;  Purpose:    Moves cursor to end of commandline.                         ;
  835. ;  Notes:      none                                                        ;
  836. ;  Entry:      BX = pointer to current position in commandline,            ;
  837. ;              CH = # of bytes to end of line.                             ;
  838. ;  Exit:       BX += CH,                                                   ;
  839. ;              CH = 0,                                                     ;
  840. ;              SI = # of characters advanced.                              ;
  841. ;  Calls:      advance_Cursor                                              ;
  842. ;  Changes:    SI,                                                         ;
  843. ;              AX, BX, CH (advance_Cursor)                                 ;
  844. ;--------------------------------------------------------------------------;
  845. PROC mov_eol
  846.  
  847.           mov       al, ch
  848.           ZERO      ah
  849.           mov       si, ax                   ; SI = CH
  850.           call      advance_Cursor
  851.           ret
  852. ENDP mov_eol
  853.  
  854.  
  855. ;----  mov_pcmd  ----------------------------------------------------------;
  856. ;  Purpose:    Replaces current with previous commandline from buffer.     ;
  857. ;  Notes:      none                                                        ;
  858. ;  Entry:      BX = pointer to current position in commandline,            ;
  859. ;              CH = # of bytes to end of line,                             ;
  860. ;              CL = # of bytes left in commandline.                        ;
  861. ;  Entry:      BX = pointer to end of new commandline,                     ;
  862. ;              CH = 0,                                                     ;
  863. ;              CL = # of bytes left in new commandline,                    ;
  864. ;              [CurCmd] adjusted.                                          ;
  865. ;  Calls:      recall_CmdFromBuf, del_line                                 ;
  866. ;  Changes:    CurCmd,                                                     ;
  867. ;              AX, BX, CH, CL, SI, (recall_CmdFromBuf)                     ;
  868. ;--------------------------------------------------------------------------;
  869. PROC mov_pcmd
  870.  
  871.           push      di
  872.  
  873. ; Point DI to 2 bytes before CurCmd. Abort if this lies at or before
  874. ; start of recall buffer.
  875.           mov       di, [cs:CurCmd]
  876.           dec       di                       ; now at possible CR
  877.           dec       di                       ; now at possible cmd's last char
  878.           cmp       di, OFFSET RecallBuf
  879.           jbe       SHORT @@Abort
  880.  
  881. ; Scan backwards to start of buffer or until finding another CR.
  882.           push      cx                       ; CH/CL for recall_CmdFromBuf
  883.           pushf
  884.           mov       al, CR
  885.           mov       cx, di
  886.           sub       cx, OFFSET RecallBuf - 1
  887.           std                                ; scan backwards
  888.           repne     scasb                    ; uses ES:DI
  889.           popf
  890.           pop       cx
  891.           inc       di                       ; should point to CR
  892.           cmp       [BYTE es:di], CR
  893.           jne       SHORT @@Abort
  894.  
  895. ; Point SI to start of command and recall it.
  896.           inc       di
  897.           mov       si, di
  898.           mov       [cs:CurCmd], si
  899.           call      recall_CmdFromBuf
  900.           jmp       SHORT @@Fin
  901.  
  902. ; Nothing to recall, so point CurCmd to start of buffer
  903. ; and delete current line.
  904. @@Abort:
  905.           mov       [cs:CurCmd], OFFSET RecallBuf
  906.           call      del_line
  907.  
  908. @@Fin:
  909.           pop       di
  910.           ret
  911. ENDP mov_pcmd
  912.  
  913.  
  914. ;----  mov_ncmd  ----------------------------------------------------------;
  915. ;  Purpose:    Replaces current with next commandline from buffer.         ;
  916. ;  Notes:      none                                                        ;
  917. ;  Entry:      BX = pointer to current position in commandline,            ;
  918. ;              CH = # of bytes to end of line,                             ;
  919. ;              CL = # of bytes left in commandline.                        ;
  920. ;  Entry:      BX = pointer to end of new commandline,                     ;
  921. ;              CH = 0,                                                     ;
  922. ;              CL = # of bytes left in new commandline,                    ;
  923. ;              [CurCmd] adjusted.                                          ;
  924. ;  Calls:      recall_CmdFromBuf, del_line                                 ;
  925. ;  Changes:    [CurCmd],                                                   ;
  926. ;              AX, BX, CH, CL, SI (recall_CmdFromBuf)                      ;
  927. ;--------------------------------------------------------------------------;
  928. PROC mov_ncmd
  929.  
  930.           push      di
  931.  
  932. ; Point DI to CurCmd. Abort if this lies at or after LastByte.
  933.           mov       di, [cs:CurCmd]
  934.           cmp       di, OFFSET LastByte
  935.           jae       SHORT @@Abort
  936.  
  937. ; Scan forwards to end of buffer or until finding another CR.
  938. ; NB: Scan stops before final CR in the recall buffer since
  939. ; there can never be a command after that; saves having to
  940. ; check DI against OFFSET LastByte.
  941.           push      cx                       ; CH/CL for recall_CmdFromBuf
  942.           mov       al, CR
  943.           mov       cx, OFFSET LastByte      ; *not* OFFSET LastByte + 1
  944.           sub       cx, di
  945.           repne     scasb                    ; uses ES:DI
  946.           pop       cx
  947.           dec       di                       ; should point to CR
  948.           cmp       [BYTE es:di], CR
  949.           jne       SHORT @@Abort
  950.  
  951. ; Point SI to start of command and recall it.
  952.           inc       di                       ; point to 1st char in next cmd
  953.           mov       si, di
  954.           mov       [cs:CurCmd], si
  955.           call      recall_CmdFromBuf
  956.           jmp       SHORT @@Fin
  957.  
  958. ; Nothing to recall, so point CurCmd to just past end
  959. ; of recall buffer and delete current line.
  960. @@Abort:
  961.           mov       [cs:CurCmd], OFFSET LastByte + 1
  962.           call      del_line
  963.  
  964. @@Fin:
  965.           pop       di
  966.           ret
  967. ENDP mov_ncmd
  968.  
  969.  
  970. ;----  del_lchar  ---------------------------------------------------------;
  971. ;  Purpose:    Deletes character to left of cursor.                        ;
  972. ;  Notes:      none                                                        ;
  973. ;  Entry:      BX = pointer to current position in commandline,            ;
  974. ;              CL = # of bytes left in commandline.                        ;
  975. ;  Exit:       BX--,                                                       ;
  976. ;              CL++.                                                       ;
  977. ;  Calls:      mov_lchar, delete_Chars                                     ;
  978. ;  Changes:    BX, (mov_lchar),                                            ;
  979. ;              AX, CH, CL, SI (delete_Chars)                               ;
  980. ;--------------------------------------------------------------------------;
  981. PROC del_lchar
  982.  
  983.           call      mov_lchar                ; sets SI = 0 (at bol) or 1
  984.           call      delete_Chars
  985.           ret
  986. ENDP del_lchar
  987.  
  988.  
  989. ;----  del_rchar  ---------------------------------------------------------;
  990. ;  Purpose:    Deletes character at cursor.                                ;
  991. ;  Notes:      none                                                        ;
  992. ;  Entry:      BX = pointer to current position in commandline,            ;
  993. ;              CH = # of bytes to end of line,                             ;
  994. ;              CL = # of bytes left in commandline.                        ;
  995. ;  Exit:       BX--,                                                       ;
  996. ;              CH--,                                                       ;
  997. ;              CL++.                                                       ;
  998. ;  Calls:      delete_Chars                                                ;
  999. ;  Changes:    AX, CH, CL, SI (delete_Chars)                               ;
  1000. ;--------------------------------------------------------------------------;
  1001. PROC del_rchar
  1002.  
  1003.           or        ch, ch
  1004.           jz        SHORT @@Fin              ; abort if already at eol
  1005.           mov       si, 1
  1006.           call      delete_Chars
  1007. @@Fin:
  1008.           ret
  1009. ENDP del_rchar
  1010.  
  1011.  
  1012. ;----  del_lword  ---------------------------------------------------------;
  1013. ;  Purpose:    Deletes word to left of cursor.                             ;
  1014. ;  Notes:      none                                                        ;
  1015. ;  Entry:      BX = pointer to current position in commandline,            ;
  1016. ;              CH = # of bytes to end of line,                             ;
  1017. ;              CL = # of bytes left in commandline.                        ;
  1018. ;  Exit:       BX, CH, and CL adjusted as appropriate.                     ;
  1019. ;  Calls:      mov_lword, delete_Chars                                     ;
  1020. ;  Changes:    BX, (mov_lword),                                            ;
  1021. ;              AX, CH, CL, SI (delete_Chars)                               ;
  1022. ;--------------------------------------------------------------------------;
  1023. PROC del_lword
  1024.  
  1025.           call      mov_lword                ; sets SI = 0 (at bol) or > 0
  1026.           call      delete_Chars
  1027.           ret
  1028. ENDP del_lword
  1029.  
  1030.  
  1031. ;----  del_rword  ---------------------------------------------------------;
  1032. ;  Purpose:    Deletes word to right of cursor.                            ;
  1033. ;  Notes:      none                                                        ;
  1034. ;  Entry:      BX = pointer to current position in commandline,            ;
  1035. ;              CH = # of bytes to end of line,                             ;
  1036. ;              CL = # of bytes left in commandline.                        ;
  1037. ;  Exit:       CH, and CL adjusted as appropriate.                         ;
  1038. ;  Calls:      find_StartofNextWord, delete_Chars                          ;
  1039. ;  Changes:    AX, CH, CL, SI (delete_Chars)                               ;
  1040. ;--------------------------------------------------------------------------;
  1041. PROC del_rword
  1042.  
  1043.           call      find_StartofNextWord     ; sets SI = 0 (at eol) or > 0
  1044.           call      delete_Chars
  1045.           ret
  1046. ENDP del_rword
  1047.  
  1048.  
  1049. ;----  del_bol  -----------------------------------------------------------;
  1050. ;  Purpose:    Deletes from cursor to start of commandline.                ;
  1051. ;  Notes:      none                                                        ;
  1052. ;  Entry:      BX = pointer to current position in commandline,            ;
  1053. ;              CH = # of bytes to end of line,                             ;
  1054. ;              CL = # of bytes left in commandline.                        ;
  1055. ;  Exit:       BX = DX + 2,                                                ;
  1056. ;              CH = number of characters in commandline,                   ;
  1057. ;              CL -= BX - DX - 2.                                          ;
  1058. ;  Calls:      mov_bol, delete_Chars                                       ;
  1059. ;  Changes:    BX, (mov_bol)                                               ;
  1060. ;              AX, CH, CL, SI (delete_Chars)                               ;
  1061. ;--------------------------------------------------------------------------;
  1062. PROC del_bol
  1063.  
  1064.           call      mov_bol                  ; sets SI = 0 (at bol) or > 0
  1065.           call      delete_Chars
  1066.           ret
  1067. ENDP del_bol
  1068.  
  1069.  
  1070. ;----  del_eol  -----------------------------------------------------------;
  1071. ;  Purpose:    Deletes from cursor to end of commandline.                  ;
  1072. ;  Notes:      none                                                        ;
  1073. ;  Entry:      BX = pointer to current position in commandline,            ;
  1074. ;              CH = # of bytes to end of line,                             ;
  1075. ;              CL = # of bytes left in commandline.                        ;
  1076. ;  Exit:       BX = DX + 2,                                                ;
  1077. ;              CH = 0,                                                     ;
  1078. ;              CL -= CH.                                                   ;
  1079. ;  Calls:      delete_Chars                                                ;
  1080. ;  Changes:    AX, CH, CL, SI (delete_Chars)                               ;
  1081. ;--------------------------------------------------------------------------;
  1082. PROC del_eol
  1083.  
  1084.           mov       al, ch
  1085.           ZERO      ah
  1086.           mov       si, ax
  1087.           call      delete_Chars
  1088.           ret
  1089. ENDP del_eol
  1090.  
  1091.  
  1092. ;----  del_line  ----------------------------------------------------------;
  1093. ;  Purpose:    Deletes entire commandline.                                 ;
  1094. ;  Notes:      none                                                        ;
  1095. ;  Entry:      BX = pointer to current position in commandline,            ;
  1096. ;              CH = # of bytes to end of line,                             ;
  1097. ;              CL = # of bytes left in commandline.                        ;
  1098. ;  Exit:       BX = DX + 2,                                                ;
  1099. ;              CH = 0,                                                     ;
  1100. ;              CL = [DX].                                                  ;
  1101. ;  Calls:      mov_bol, del_eol                                            ;
  1102. ;  Changes:    BX, (mov_bol)                                               ;
  1103. ;              AX, CH, CL, SI (del_eol)                                    ;
  1104. ;--------------------------------------------------------------------------;
  1105. PROC del_line
  1106.  
  1107.           call      mov_bol
  1108.           call      del_eol
  1109.           ret
  1110. ENDP del_line
  1111.  
  1112.  
  1113. ;----  del_buf  -----------------------------------------------------------;
  1114. ;  Purpose:    Deletes all commands in recall buffer.                      ;
  1115. ;  Notes:      Does not affect current commandline.                        ;
  1116. ;              This function is not documented elsewhere.                  ;
  1117. ;  Entry:      n/a                                                         ;
  1118. ;  Exit:       [CurCmd] = OFFSET LastByte + 1.                             ;
  1119. ;  Calls:      init_Buf                                                    ;
  1120. ;  Changes:    AX, [CurCmd] (init_Buf)                                     ;
  1121. ;--------------------------------------------------------------------------;
  1122. PROC del_buf
  1123.  
  1124.           mov       [WORD cs:CurCmd], 0
  1125.           call      init_Buf                 ; nb: changes CurCmd
  1126.           ret
  1127. ENDP del_buf
  1128.  
  1129.  
  1130. ;----  toggle_InsMode  ----------------------------------------------------;
  1131. ;  Purpose:    Toggles flag for insert mode.                               ;
  1132. ;  Notes:      none                                                        ;
  1133. ;  Entry:      n/a                                                         ;
  1134. ;  Exit:       [InsMode] toggled.                                          ;
  1135. ;  Calls:      none                                                        ;
  1136. ;  Changes:    [InsMode]                                                   ;
  1137. ;--------------------------------------------------------------------------;
  1138. PROC toggle_InsMode
  1139.  
  1140.           xor       [cs:InsMode], 1
  1141.           ret
  1142. ENDP toggle_InsMode
  1143.  
  1144.  
  1145. ;----  init_Buf  ----------------------------------------------------------;
  1146. ;  Purpose:    Initializes recall buffer if necessary.                     ;
  1147. ;  Notes:      Clears recall buffer if CurCmd is zero. Normally, CurCmd    ;
  1148. ;                   will take on values OFFSET RecallBuf and LastByte,     ;
  1149. ;                   or less. Zero should not otherwise occur.              ;
  1150. ;              This is needed when scanning for previous commands -        ;
  1151. ;                   spurious CRs should not be encountered.                ;
  1152. ;  Entry:      [CurCmd] = pointer to current command in recall buffer.     ;
  1153. ;  Exit:       [CurCmd] = OFFSET LastByte + 1 if buffer is initialized.    ;
  1154. ;  Calls:      none                                                        ;
  1155. ;  Changes:    AX, [CurCmd] possibly                                       ;
  1156. ;--------------------------------------------------------------------------;
  1157. PROC init_Buf
  1158.  
  1159. ; Abort if [CurCmd] is non-zero.
  1160.           cmp       [WORD cs:CurCmd], 0
  1161.           jne       SHORT @@Fin
  1162.  
  1163. ; Initialize buffer by zeroing out all but last byte. There put a CR
  1164. ; so when searching backwards for commands I'll find at least one.
  1165.           push      cx di
  1166.           ZERO      al                       ; fill with zeros
  1167.           mov       cx, BUFSIZE - 1
  1168.           mov       di, OFFSET RecallBuf
  1169.           rep       stosb                    ; uses ES:DI
  1170.           mov       [BYTE es:di], CR         ; buffer ends with CR
  1171.           pop       di cx
  1172.  
  1173. ; Point current command to past end of recall buffer. This is so both
  1174. ; mov_pcmd and mov_ncmd will not find any commands yet still function
  1175. ; without error (which would happen if [CurCmd] were left at 0).
  1176.           mov       [cs:CurCmd], OFFSET LastByte + 1
  1177.  
  1178. @@Fin:
  1179.           ret
  1180. ENDP init_Buf
  1181.  
  1182.  
  1183. ;----  get_CmdLine  -------------------------------------------------------;
  1184. ;  Purpose:    Reads a commandline from user.                              ;
  1185. ;  Notes:      The caller's buffer is used as a scratch area to keep       ;
  1186. ;                   memory requirements to a minimum.                      ;
  1187. ;  Entry:      DS:DX = buffer for storing commandline,                     ;
  1188. ;              [BYTE DS:DX] = maximum number of bytes to read.             ;
  1189. ;  Exit:       [BYTE DS:DX+1] = number of bytes actually read,             ;
  1190. ;              [BYTE DS:DX+2] = 1st byte read from user.                   ;
  1191. ;  Calls:      get_KeyNoEcho, add_CharToLine, [CmdTbl]                     ;
  1192. ;  Changes:    AX, BX, CX, BP, [InsMode], [PrevCmd], [NextCmd]             ;
  1193. ;--------------------------------------------------------------------------;
  1194. PROC get_CmdLine
  1195.  
  1196.           mov       bx, dx                   ; BX used for indexed addressing
  1197.           ZERO      ch                       ; bytes to end of line
  1198.           mov       cl, [bx]                 ; space left in buffer
  1199.           dec       cl                       ;    less 1 for final CR
  1200.           inc       bx                       ; pointer to first spot in buffer
  1201.           inc       bx
  1202.  
  1203. ; Get key and determine if it's an editing key or a regular character.
  1204. @@NewKey:
  1205.           call      get_KeyNoEcho            ; get key from user
  1206.           cmp       al, CR                   ; is user done yet?
  1207.           jz        SHORT @@Fin
  1208.           cmp       al, LF                   ; skip LF if stdin redirected
  1209.           jz        SHORT @@NewKey
  1210.           cmp       al, BS                   ; BS is an editing key
  1211.           je        SHORT @@EditKey
  1212.           cmp       al, ESCAPE               ; ESCAPE is another
  1213.           je        SHORT @@EditKey
  1214.           or        al, al                   ; was key zero?
  1215.           jnz       SHORT @@RegularChar      ;   no, then it's a regular char
  1216.           call      get_KeyNoEcho            ;   yes, get extended scan code
  1217.  
  1218. ; Process extended key as an editing key. Invalid keys are not added
  1219. ; to the commandline buffer; instead, they merely result in a bell.
  1220. @@EditKey:
  1221.           mov       bp, OFFSET CmdTbl        ; point to table of editing cmds
  1222.  
  1223. @@NewCmd:
  1224.           cmp       [(CMD PTR cs:bp).Key], al
  1225.           je        SHORT @@ProcessCmd
  1226.           add       bp, SIZE CmdTbl
  1227.           cmp       [(CMD PTR cs:bp).Key], 0 ; zero marks end of table
  1228.           jne       SHORT @@NewCmd           ;   and must point to ring_Bell
  1229.                                              ;   so execution drops thru!!!
  1230. @@ProcessCmd:
  1231.           call      [(CMD PTR cs:bp).Function]
  1232.           jmp       SHORT @@NewKey
  1233.  
  1234. ; It's an ordinary character so add it to the commandline.
  1235. @@RegularChar:
  1236.           call      add_CharToLine
  1237.           jmp       SHORT @@NewKey
  1238.  
  1239. ; Now determine number of bytes in buffer, put that count in [DX+1], 
  1240. ; and terminate buffer with a CR. NB: count excludes final CR.
  1241. @@Fin:
  1242.           mov       bx, dx                   ; point back to start of buffer
  1243.           mov       al, [bx]                 ; compute count
  1244.           sub       al, cl
  1245.           dec       al                       ; exclude final CR
  1246.           ZERO      ah
  1247.           mov       [bx+1], al               ; [DS:DX+1] = count
  1248.           add       bx, ax
  1249.           mov       [BYTE bx+2], CR          ; place CR at end of buffer
  1250.           ret
  1251. ENDP get_CmdLine
  1252.  
  1253.  
  1254. ;----  store_CmdInBuf  ----------------------------------------------------;
  1255. ;  Purpose:    Stores the commandline at the end of the recall buffer.     ;
  1256. ;  Notes:      Commandlines consisting of 1 character (CR) are not saved.  ;
  1257. ;  Entry:      DS:DX = pointer to start of commandline,                    ;
  1258. ;              [BYTE DS:DX+1] = maximum number of bytes to read.           ;
  1259. ;  Exit:       [CurCmd] = LastByte + 1.                                    ;
  1260. ;  Calls:      none                                                        ;
  1261. ;  Changes:    AX, BX, CX, DI, SI, [CurCmd]                                ;
  1262. ;--------------------------------------------------------------------------;
  1263. PROC store_CmdInBuf
  1264.  
  1265. ; Check length of commandline.
  1266.           mov       bx, dx
  1267.           mov       cl, [bx+1]
  1268.           or        cl, cl                   ; CL does not include final CR
  1269.           jz        SHORT @@Fin              ; so if = 0, nothing's there
  1270.           inc       cl                       ; else set CX = # bytes in cmd
  1271.           ZERO      ch                       ;   *including* final CR
  1272.  
  1273. ; Make room in recall buffer for commandline by shifting everything
  1274. ; back by [DS:DX+1] characters. 
  1275.           push      cx ds                    ; need both to copy to recall buf
  1276.           mov       ax, cs
  1277.           mov       ds, ax
  1278.           mov       di, OFFSET RecallBuf     ; to start of buffer
  1279.           mov       si, di
  1280.           add       si, cx                   ; from start + CX
  1281.           neg       cx
  1282.           add       cx, BUFSIZE              ; for BUFSIZE - [BYTE BX+1]
  1283.           rep       movsb                    ; move them
  1284.           pop       ds cx
  1285.  
  1286. ; Add commandline in empty space at end of recall buffer. By this
  1287. ; point DI will point to space for current commandline.
  1288.           mov       si, bx
  1289.           inc       si
  1290.           inc       si
  1291.           rep       movsb                    ; from DS:SI to ES:DI
  1292.           mov       [cs:CurCmd], OFFSET LastByte + 1
  1293.  
  1294. @@Fin:
  1295.           ret
  1296. ENDP store_CmdInBuf
  1297.  
  1298.  
  1299. ;----  do_Int21  ----------------------------------------------------------;
  1300. ;  Purpose:    Passes calls to input strings along to my own handler.      ;
  1301. ;  Notes:      none                                                        ;
  1302. ;  Entry:      AH = subfunction to perform                                 ;
  1303. ;  Exit:       If AH = 10, DS:DX points to buffer read from user.          ;
  1304. ;  Calls:      init_Buf, get_CmdLine, store_CmdInBuf                       ;
  1305. ;  Changes:    flags                                                       ;
  1306. ;--------------------------------------------------------------------------;
  1307. PROC do_Int21   FAR
  1308.  
  1309. ; This structure is used to share intrrupts. The real entry point
  1310. ; follows immediately after it.
  1311. my_Int21  ISR       < , , , 0, ((@@hw_reset - $ - 2) SHL 8 + 0ebh), >
  1312.  
  1313. ; If the call is for buffered input, then use my handler;
  1314. ; otherwise, pass it along to the old handler.
  1315.           cmp       ah, 10
  1316.           jz        SHORT @@SwitchStack
  1317.           jmp       [cs:my_Int21.OldISR]     ;   no, pass it along
  1318.                                              ;      nb: old vector issues IRET
  1319.  
  1320. ; Switch over to my own stack and save callers registers.
  1321. @@SwitchStack:
  1322.           mov       [cs:OldAX], ax           ; can't push it on my stack yet
  1323.           cli                                ; critical part - disallow INTs
  1324.           mov       [WORD cs:OldStack], sp
  1325.           mov       [WORD cs:OldStack+2], ss
  1326.           mov       ax, cs
  1327.           mov       ss, ax
  1328.           mov       sp, OFFSET StackTop
  1329.           sti                                ; ok, out of critical section
  1330.           push      bx cx dx di si bp ds es
  1331.  
  1332. ; Meat of my interrupt handler.
  1333.           mov       es, ax                   ; set ES = CX
  1334.           cld
  1335.           call      init_Buf
  1336.           call      get_CmdLine
  1337.           call      store_CmdInBuf
  1338.  
  1339. ; Restore caller's registers.
  1340.           pop       es ds bp si di dx cx bx
  1341.           cli
  1342.           mov       ss, [WORD cs:OldStack+2]
  1343.           mov       sp, [WORD cs:OldStack]
  1344.           sti
  1345.           mov       ax, [cs:OldAX]
  1346.  
  1347.           iret                               ; return to caller
  1348.  
  1349. ; Required for IBM Interrupt Sharing Protocol. Normally it is used
  1350. ; only by hardware interrupt handlers.
  1351. @@hw_reset:
  1352.           retf
  1353. ENDP do_Int21
  1354.  
  1355.  
  1356. ;----  do_Int2D  ----------------------------------------------------------;
  1357. ;  Purpose:    Handle INT 2D.                                              ;
  1358. ;  Notes:      Only the install check is truly supported.                  ;
  1359. ;  Entry:      AH = Multiplex ID,                                          ;
  1360. ;              AL = function code                                          ;
  1361. ;  Exit:       AL = FF in the case of an install check,                    ;
  1362. ;              CX = TSR version,                                           ;
  1363. ;              DX:DI points to resident copy of TSR signature.             ;
  1364. ;  Calls:      n/a                                                         ;
  1365. ;  Changes:    AL, CX, DX, DI                                              ;
  1366. ;--------------------------------------------------------------------------;
  1367. PROC do_Int2D  FAR
  1368.  
  1369. ; This structure is used to share intrrupts. The real entry point
  1370. ; follows immediately after it.
  1371. my_Int2D  ISR       < , , , 0, ((@@hw_reset - $ - 2) SHL 8 + 0ebh), >
  1372.  
  1373. ; Test if request is for me. Pass it along to next ISR in chain if not.
  1374.           cmp       ah, [cs:MPlex]           ; my multiplex ID?
  1375.           jz        SHORT @@forMe            ;   yes
  1376.           jmp       [cs:my_Int2D.OldISR]     ;   no, pass it along
  1377.                                              ;      nb: old vector issues IRET
  1378.  
  1379. ; Check function as specified in AL.
  1380. @@forMe:
  1381.           cmp       al, 0                    ; installation check
  1382.           jz        SHORT @@InstallCheck
  1383.           cmp       al, 1                    ; get entry point
  1384.           jz        SHORT @@GetEntryPoint
  1385.           cmp       al, 2                    ; uninstall
  1386.           jz        SHORT @@Uninstall
  1387.           ZERO      al                       ; mark as not implemented
  1388.           jmp       SHORT @@Fin
  1389.  
  1390. @@InstallCheck:
  1391.           dec       al                       ; set AL = FF
  1392.           mov       cx, [cs:TSR_Ver]         ; CH = major; CL = minor
  1393.           mov       dx, cs                   ; DX:DI points to sig string
  1394.           mov       di, OFFSET TSR_Sig
  1395.           jmp       SHORT @@Fin
  1396.  
  1397. @@GetEntryPoint:
  1398.           ZERO      al                       ; mark as not supported
  1399.           jmp       SHORT @@Fin
  1400.  
  1401. @@Uninstall:
  1402.           ZERO      al                       ; not implemented in API
  1403.           jmp       SHORT @@Fin
  1404.  
  1405. @@Fin:
  1406.           iret                               ; return to caller
  1407.  
  1408. ; Required for IBM Interrupt Sharing Protocol. Normally it is used
  1409. ; only by hardware interrupt handlers.
  1410. @@hw_reset:
  1411.           retf
  1412. ENDP do_Int2D
  1413.  
  1414.  
  1415. %NEWPAGE
  1416. ;--------------------------------------------------------------------------;
  1417. ;                        R E C A L L   B U F F E R                         ;
  1418. ;--------------------------------------------------------------------------;
  1419. RecallBuf =         $                        ; will overlay transient portion
  1420. LastByte  =         RecallBuf + BUFSIZE - 1  ; room for BUFSIZE characters
  1421.                                              ; and end of resident portion
  1422.  
  1423.  
  1424. %NEWPAGE
  1425. ;--------------------------------------------------------------------------;
  1426. ;                       T R A N S I E N T   D A T A                        ;
  1427. ;--------------------------------------------------------------------------;
  1428. ProgName  DB        'recall: '
  1429.           DB        EOS
  1430. EOL       DB        '.', CR, LF
  1431.           DB        EOS
  1432. HelpMsg   DB        CR, LF
  1433.           DB        'TifaWARE RECALL, v', VERS_STR, ', ', ??date
  1434.           DB        ' - commandline editor and history TSR.', CR, LF
  1435.           DB        'Usage: recall [-options]', CR, LF, LF
  1436.           DB        'Options:', CR, LF
  1437.           DB        '  -i = install in memory', CR, LF
  1438.           DB        '  -l = list commandlines in recall buffer', CR, LF
  1439.           DB        '  -r = remove from memory', CR, LF
  1440.           DB        '  -? = display this help message', CR, LF, LF
  1441.           DB        'Only one option can be specified at a time.'
  1442.           DB        CR, LF, EOS
  1443. ErrMsgOpt DB        'illegal option -- '
  1444. OptCh     DB        ?                        ; room for offending character
  1445.           DB        EOS
  1446. ErrMsgVer DB        'DOS v1 is not supported'
  1447.           DB        EOS
  1448. ErrMsgRes DB        'unable to go resident'
  1449.           DB        EOS
  1450. ErrMsgRem DB        'unable to remove from memory'
  1451.           DB        EOS
  1452. ErrMsgNYI DB        'not yet installed'
  1453.           DB        EOS
  1454. InstalMsg DB        'TifaWARE RECALL, v', VERS_STR
  1455.           DB        ' now installed.'
  1456.           DB        CR, LF, EOS
  1457. RemoveMsg DB        'successfully removed'
  1458.           DB        EOS
  1459.  
  1460. SwitCh    DB        '-'                      ; char introducing options
  1461. HFlag     DB        0                        ; flag for on-line help
  1462. IFlag     DB        0                        ; flag for installing TSR
  1463. LFlag     DB        0                        ; flag for listing commandlines
  1464. RFlag     DB        0                        ; flag for removing TSR
  1465.  
  1466.  
  1467. %NEWPAGE
  1468. ;--------------------------------------------------------------------------;
  1469. ;                       T R A N S I E N T   C O D E                        ;
  1470. ;--------------------------------------------------------------------------;
  1471. ;----  go_Resident  -------------------------------------------------------;
  1472. ;  Purpose:    Attempts to make TSR resident.                              ;
  1473. ;  Notes:      Aborts if there's not enough memory to satisfy request.     ;
  1474. ;              This procedure ONLY EXITS ON ERROR.                         ;
  1475. ;  Entry:      DS = segment address of program's PSP, which also holds     ;
  1476. ;                   HookTbl, a structure of type ISRHOOK.                  ;
  1477. ;  Exit:       none                                                        ;
  1478. ;  Calls:      check_ifInstalled, fputs, fake_Env, install_TSR, errmsg     ;
  1479. ;  Changes:    AX, BX, CX, DX, DI, SI, ES                                  ;
  1480. ;--------------------------------------------------------------------------;
  1481. PROC go_Resident
  1482.  
  1483. ; See if there's already a copy resident. nb: only interested in AX
  1484. ; on return from the install check.
  1485.           mov       si, OFFSET TSR_Sig
  1486.           call      check_ifInstalled        ; -> AX, CX, and DX:DI
  1487.           cmp       al, 2                    ; out of multiplex ids?
  1488.           jz        SHORT @@Abort            ;   yes, abort
  1489.           cmp       al, 1                    ; already loaded?
  1490.           jz        SHORT @@Abort            ;   yes
  1491.           mov       [MPlex], ah              ; save mplex id
  1492.  
  1493. ; This is the point of no-return -- if we get here we're going resident.
  1494.           mov       bx, STDOUT
  1495.           mov       dx, OFFSET InstalMsg
  1496.           call      fputs
  1497.  
  1498. ; Create a fake environment and free existing one.
  1499. ; Make sure that ES points to PSP.
  1500.           ZERO      cx                       ; tells fake_Env to fake it
  1501.           push      ds
  1502.           pop       es
  1503.           call      fake_Env
  1504.  
  1505. ; Ok, all that's left is to go resident.
  1506. ; ****************************************************************************
  1507. ; NB: TASM's IDEAL mode treats arguments of the OFFSET operator in a peculiar 
  1508. ; fashion, as can be seen by browsing the lexical grammer in Appendix A of
  1509. ; the _Reference Guide_. If MASM mode were used, the expression below would
  1510. ; be written "(OFFSET LastByte - OFFSET SegStart + 16) SHR 5". However, in
  1511. ; IDEAL mode not only would "OFFSET SegStart + 16" be parsed as "OFFSET
  1512. ; (SegStart + 16)" but also the result would be viewed as a relative quantity.
  1513. ; As it is, TASM replaces labels below with their respective address values 
  1514. ; thereby computing the "correct" amount of memory to save.
  1515. ; ****************************************************************************
  1516. ; NB: While Angermayer and Jaeger in their book say 15 should be used
  1517. ; below, I've found 16 is necessary to handle cases in which LastByte
  1518. ; lies at the start of a paragraph. So what if I'm wasting an entire
  1519. ; paragraph!
  1520. ; ****************************************************************************
  1521.           mov       dx, (LastByte - SegStart + 16) SHR 4
  1522.           mov       bx, OFFSET HookTbl       ; pointer to ISRHOOK structure
  1523.           call      install_TSR              ; never returns
  1524.  
  1525. ; Execution gets here only on error because:
  1526. ;  - all multiplex ids are in use! 
  1527. ;  - the TSR is already resident.
  1528. @@Abort:
  1529.           mov       dx, OFFSET ErrMsgRes     ; "unable to go resident"
  1530.           call      errmsg
  1531.           ret
  1532. ENDP go_Resident
  1533.  
  1534.  
  1535. ;----  clear_Resident  ----------------------------------------------------;
  1536. ;  Purpose:    Attempts to remove a TSR from memory.                       ;
  1537. ;  Notes:      none                                                        ;
  1538. ;  Entry:      DS = segment address of program's PSP.                      ;
  1539. ;  Exit:       AL = 0 if removal succeeded; ERRNYI if not installed;       ;
  1540. ;                   ERRUNI otherwise.                                      ;
  1541. ;  Calls:      check_ifInstalled, remove_TSR, errmsg                       ;
  1542. ;  Changes:    AX, BX, CX, DX, DI, SI, ES                                  ;
  1543. ;--------------------------------------------------------------------------;
  1544. PROC clear_Resident
  1545.  
  1546. ; See if there's already a copy resident.
  1547.           mov       si, OFFSET TSR_Sig
  1548.           call      check_ifInstalled        ; DS:SI -> AX, CX, DX:DI
  1549.           cmp       al, 1                    ; already loaded?
  1550.           jz        SHORT @@Removal          ;   yes
  1551.           mov       al, ERRNYI               ;   no, set return code
  1552.           mov       dx, OFFSET ErrMsgNYI     ;     "not yet installed"
  1553.           jmp       SHORT @@Fin
  1554.  
  1555. ; Try to remove it.
  1556. @@Removal:
  1557.           mov       bx, OFFSET HookTbl       ; HookTbl in resident data area
  1558.           mov       es, dx                   ; install check returns DX:DI
  1559.           call      remove_TSR               ; ES:BX -> n/a
  1560.           jc        SHORT @@Abort
  1561.           ZERO      al
  1562.           mov       dx, OFFSET RemoveMsg
  1563.           jmp       SHORT @@Fin
  1564.  
  1565. @@Abort:
  1566.           mov       al, ERRUNI
  1567.           mov       dx, OFFSET ErrMsgRem     ; "unable to remove"
  1568.  
  1569. @@Fin:
  1570.           call      errmsg
  1571.           ret
  1572. ENDP clear_Resident
  1573.  
  1574.  
  1575. ;----  list_CmdLines  -----------------------------------------------------;
  1576. ;  Purpose:    Lists commandlines in recall buffer.                        ;
  1577. ;  Notes:      none                                                        ;
  1578. ;  Entry:      none                                                        ;
  1579. ;  Exit:       AL = 0 if successful; ERRNYI otherwise.                     ;
  1580. ;  Calls:      check_ifInstalled, errmsg                                   ;
  1581. ;  Changes:    AX, CX, DX, DI, SI, ES                                      ;
  1582. ;--------------------------------------------------------------------------;
  1583. PROC list_CmdLines
  1584.  
  1585.           mov       si, OFFSET TSR_Sig
  1586.           call      check_ifInstalled        ; DS:SI -> AX, CX, DX:DI
  1587.           cmp       al, 1                    ; already loaded?
  1588.           jnz       SHORT @@Abort            ;   no
  1589.           push      ds
  1590.           mov       ds, dx                   ; point DS into resident data
  1591.           mov       es, dx                   ; ES too
  1592.  
  1593. ; Point to start of 1st complete command in recall buffer. NB:
  1594. ; there will always be at least one command - "recall -l".
  1595.           mov       al, CR
  1596.           mov       cx, BUFSIZE
  1597.           mov       di, OFFSET RecallBuf
  1598.           repne     scasb                    ; uses ES:DI
  1599.  
  1600. ; Display rest of buffer. NB: This is done character one character at
  1601. ; a time because buffered lines end with CR, not 0.
  1602.           mov       ah, 2                    ; DOS subfunction to display char
  1603. @@NextChar:
  1604.           mov       dl, [di]                 ; get char
  1605.           inc       di
  1606.           int       DOS                      ; display it
  1607.           cmp       dl, CR                   ; need to display CR/LF?
  1608.           loopne    SHORT @@NextChar         ; always decrements CX 
  1609.           mov       dl, LF                   ; display LF now
  1610.           int       DOS
  1611.           or        cx, cx                   ; done yet?
  1612.           jnz       SHORT @@NextChar
  1613.  
  1614.           pop       ds
  1615.           ZERO      al                       ; flag no error
  1616.           jmp       SHORT @@Fin
  1617.  
  1618. @@Abort:
  1619.           mov       dx, OFFSET ErrMsgNYI
  1620.           call      errmsg
  1621.           mov       al, ERRNYI
  1622.  
  1623. @@Fin:
  1624.           ret
  1625. ENDP list_CmdLines
  1626.  
  1627.  
  1628. ;----  skip_Spaces  -------------------------------------------------------;
  1629. ;  Purpose:    Skips past spaces in a string.                              ;
  1630. ;  Notes:      Scanning stops with either a non-space *OR* CX = 0.         ;
  1631. ;  Entry:      DS:SI = start of string to scan.                            ;
  1632. ;  Exit:       AL = next non-space character,                              ;
  1633. ;              CX is adjusted as necessary,                                ;
  1634. ;              DS:SI = pointer to next non-space.                          ;
  1635. ;  Calls:      none                                                        ;
  1636. ;  Changes:    AL, CX, SI                                                  ;
  1637. ;--------------------------------------------------------------------------;
  1638. PROC skip_Spaces
  1639.  
  1640.           jcxz      SHORT @@Fin
  1641. @@NextCh:
  1642.           lodsb
  1643.           cmp       al, ' '
  1644.           loopz     @@NextCh
  1645.           jz        SHORT @@Fin              ; CX = 0; don't adjust
  1646.  
  1647.           inc       cx                       ; adjust counters if cx > 0
  1648.           dec       si
  1649.  
  1650. @@Fin:
  1651.           ret
  1652. ENDP skip_Spaces
  1653.  
  1654.  
  1655. ;----  get_Opt  -----------------------------------------------------------;
  1656. ;  Purpose:    Get a commandline option.                                   ;
  1657. ;  Notes:      none                                                        ;
  1658. ;  Entry:      AL = option character,                                      ;
  1659. ;  Exit:       n/a                                                         ;
  1660. ;  Calls:      tolower, errmsg                                             ;
  1661. ;  Changes:    AX, DX, [OptCh], [HFlag], [IFlag], [LFlag], [RFlag]         ;
  1662. ;--------------------------------------------------------------------------;
  1663. PROC get_Opt
  1664.  
  1665.           mov       [OptCh], al              ; save for later
  1666.           call      tolower                  ; use only lowercase in cmp.
  1667.           cmp       al, 'i'
  1668.           jz        SHORT @@OptI
  1669.           cmp       al, 'l'
  1670.           jz        SHORT @@OptL
  1671.           cmp       al, 'r'
  1672.           jz        SHORT @@OptR
  1673.           cmp       al, '?'
  1674.           jz        SHORT @@OptH
  1675.           mov       dx, OFFSET ErrMsgOpt     ; unrecognized option
  1676.           call      errmsg                   ; then *** DROP THRU *** to OptH
  1677.  
  1678. ; Various possible options.
  1679. @@OptH:
  1680.           mov       [HFlag], ON              ; set help flag
  1681.           jmp       SHORT @@Fin
  1682.  
  1683. @@OptI:
  1684.           mov       [IFlag], ON              ; install in memory
  1685.           jmp       SHORT @@Fin
  1686.  
  1687. @@OptL:
  1688.           mov       [LFlag], ON              ; list cmds in recall buffer
  1689.           jmp       SHORT @@Fin
  1690.  
  1691. @@OptR:
  1692.           mov       [RFlag], ON              ; remove from memory
  1693.  
  1694. @@Fin:
  1695.           ret
  1696. ENDP get_Opt
  1697.  
  1698.  
  1699. ;----  process_CmdLine  ---------------------------------------------------;
  1700. ;  Purpose:    Processes commandline arguments.                            ;
  1701. ;  Notes:      A switch character by itself is ignored.                    ;
  1702. ;  Entry:      n/a                                                         ;
  1703. ;  Exit:       n/a                                                         ;
  1704. ;  Calls:      skip_Spaces, get_Opt                                        ;
  1705. ;  Changes:    AX, CX, SI,                                                 ;
  1706. ;              DX, [OptCh], [HFlag], [IFlag], [LFlag], [RFlag] (get_Opt)   ;
  1707. ;              Direction flag is cleared.                                  ;
  1708. ;--------------------------------------------------------------------------;
  1709. PROC process_CmdLine
  1710.  
  1711.           cld                                ; forward, march!
  1712.           ZERO      ch
  1713.           mov       cl, [CmdLen]             ; length of commandline
  1714.           mov       si, OFFSET CmdLine       ; offset to start of commandline
  1715.  
  1716.           call      skip_Spaces              ; check if any args supplied
  1717.           or        cl, cl
  1718.           jnz       SHORT @@ArgLoop
  1719.  
  1720.           mov       [HFlag], ON              ; assume user needs help
  1721.           jmp       SHORT @@Fin
  1722.  
  1723. ; For each blank-delineated argument on the commandline...
  1724. @@ArgLoop:
  1725.           lodsb                              ; next character
  1726.           dec       cl
  1727.           cmp       al, [SwitCh]             ; is it the switch character?
  1728.           jnz       SHORT @@NonOpt           ;   no
  1729.  
  1730. ; Isolate each option and process it. Stop when a space is reached.
  1731. @@OptLoop:
  1732.           jcxz      SHORT @@Fin              ; abort if nothing left
  1733.           lodsb
  1734.           dec       cl
  1735.           cmp       al, ' '
  1736.           jz        SHORT @@NextArg          ; abort when space reached
  1737.           call      get_Opt
  1738.           jmp       @@OptLoop
  1739.  
  1740. ; Any argument which is *not* an option is invalid. Set help flag and abort.
  1741. @@NonOpt:
  1742.           mov       [HFlag], ON
  1743.           jmp       SHORT @@Fin
  1744.  
  1745. ; Skip over spaces until next argument is reached.
  1746. @@NextArg:
  1747.           call      skip_Spaces
  1748.           or        cl, cl
  1749.           jnz       @@ArgLoop
  1750.  
  1751. @@Fin:
  1752.           ret
  1753. ENDP process_CmdLine
  1754.  
  1755.  
  1756. ;----  main  --------------------------------------------------------------;
  1757. ;  Purpose:    Main section of program.                                    ;
  1758. ;  Notes:      none                                                        ;
  1759. ;  Entry:      Arguments as desired                                        ;
  1760. ;  Exit:       Return code as follows:                                     ;
  1761. ;                   0 => program ran successfully,                         ;
  1762. ;                   ERRH => on-line help supplied,                         ;
  1763. ;                   ERRINS => program could not be installed,              ;
  1764. ;                   ERRNYI => program was not yet installed.               ;
  1765. ;  Calls:      process_CmdLine, fputs, list_CmdLines, install_TSR,         ;
  1766. ;                   uninstall_TSR                                          ;
  1767. ;  Changes:    n/a                                                         ;
  1768. ;--------------------------------------------------------------------------;
  1769. main:
  1770.  
  1771. ; Must be running at least DOS v2.x.
  1772.           call      getvdos
  1773.           cmp       al, 2
  1774.           jae       SHORT @@ReadCmds
  1775.           mov       dx, OFFSET ErrMsgVer     ; gotta have at least DOS v2
  1776.           call      errmsg
  1777.  
  1778. ; Parse commandline.
  1779. @@ReadCmds:
  1780.           call      process_CmdLine          ; process commandline args
  1781.           cmp       [IFlag], ON              ; install it?
  1782.           je        SHORT @@Install
  1783.           cmp       [LFlag], ON              ; list commands?
  1784.           je        SHORT @@List
  1785.           cmp       [RFlag], ON              ; remove it?
  1786.           je        SHORT @@Remove
  1787.           mov       bx, STDERR               ; user must need help
  1788.           mov       dx, OFFSET HelpMsg
  1789.           call      fputs
  1790.           mov       al, ERRH
  1791.           jmp       SHORT @@Fin
  1792.  
  1793. @@List:
  1794.           call      list_CmdLines
  1795.           jmp       SHORT @@Fin
  1796.  
  1797. @@Install:
  1798.           call      go_Resident              ; returns on error only
  1799.           mov       al, ERRINS
  1800.           jmp       SHORT @@Fin
  1801.  
  1802. @@Remove:
  1803.           call      clear_Resident
  1804.  
  1805. ; Terminate the program using as return code what's in AL.
  1806. @@Fin:
  1807.           mov       ah, 4ch
  1808.           int       DOS
  1809. EVEN
  1810. ;-------------------------------------------------------------------------;
  1811. ;  Purpose:    Writes an ASCIIZ string to specified device.
  1812. ;  Notes:      A zero-length string doesn't seem to cause problems when
  1813. ;                 this output function is used.
  1814. ;  Requires:   8086-class CPU and DOS v2.0 or better.
  1815. ;  Entry:      BX = device handle,
  1816. ;              DS:DX = pointer to string.
  1817. ;  Exit:       Carry flag set if EOS wasn't found or handle is invalid.
  1818. ;  Calls:      strlen
  1819. ;  Changes:    none
  1820. ;-------------------------------------------------------------------------;
  1821. PROC fputs
  1822.  
  1823.    push     ax cx di es
  1824.    mov      ax, ds
  1825.    mov      es, ax
  1826.    mov      di, dx
  1827.    call     strlen                        ; set CX = length of string
  1828.    jc       SHORT @@Fin                   ; abort if problem finding end
  1829.    mov      ah, 40h                       ; MS-DOS raw output function
  1830.    int      DOS
  1831. @@Fin:
  1832.    pop      es di cx ax
  1833.    ret
  1834.  
  1835. ENDP fputs
  1836.  
  1837.  
  1838. EVEN
  1839. ;-------------------------------------------------------------------------;
  1840. ;  Purpose:    Writes an error message to stderr.
  1841. ;  Notes:      none
  1842. ;  Requires:   8086-class CPU and DOS v2.0 or better.
  1843. ;  Entry:      DS:DX = pointer to error message.
  1844. ;  Exit:       n/a
  1845. ;  Calls:      fputs
  1846. ;  Changes:    none
  1847. ;-------------------------------------------------------------------------;
  1848. PROC errmsg
  1849.  
  1850.    push     bx dx
  1851.    mov      bx, STDERR
  1852.    mov      dx, OFFSET ProgName           ; display program name
  1853.    call     fputs
  1854.    pop      dx                            ; recover calling parameters
  1855.    push     dx                            ; and save again to avoid change
  1856.    call     fputs                         ; display error message
  1857.    mov      dx, OFFSET EOL
  1858.    call     fputs
  1859.    pop      dx bx
  1860.    ret
  1861.  
  1862. ENDP errmsg
  1863.  
  1864.  
  1865. EVEN
  1866. ;-------------------------------------------------------------------------;
  1867. ;  Purpose:    Gets version of DOS currently running.
  1868. ;  Notes:      none
  1869. ;  Requires:   8086-class CPU and DOS v2.0 or better.
  1870. ;  Entry:      n/a
  1871. ;  Exit:       AL = major version number,
  1872. ;              AH = minor version number (2.1 = 10).
  1873. ;  Calls:      none
  1874. ;  Changes:    AX
  1875. ;-------------------------------------------------------------------------;
  1876. PROC getvdos
  1877.  
  1878.    push     bx cx                         ; DOS destroys bx and cx!
  1879.    mov      ah, 30h
  1880.    int      DOS
  1881.    pop      cx bx
  1882.    ret
  1883.  
  1884. ENDP getvdos
  1885.  
  1886.  
  1887. EVEN
  1888. ;--------------------------------------------------------------------------;
  1889. ;  Purpose:    Gets address of an interrupt handler.
  1890. ;  Notes:      none
  1891. ;  Requires:   8086-class CPU and DOS v2.0 or better.
  1892. ;  Entry:      AL = interrupt of interest.
  1893. ;  Exit:       ES:BX = address of current interrupt handler
  1894. ;  Calls:      none
  1895. ;  Changes:    ES:BX
  1896. ;--------------------------------------------------------------------------;
  1897. PROC getvect
  1898.  
  1899.    push     ax
  1900.    mov      ah, 35h                       ; find address of handler
  1901.    int      DOS                           ; returned in ES:BX
  1902.    pop      ax
  1903.    ret
  1904. ENDP getvect
  1905.  
  1906.  
  1907. ;--------------------------------------------------------------------------;
  1908. ;  Purpose:    Sets an interrupt vector.
  1909. ;  Notes:      none
  1910. ;  Requires:   8086-class CPU and DOS v1.0 or better.
  1911. ;  Entry:      AL = interrupt of interest,
  1912. ;              DS:DX = address of new interrupt handler
  1913. ;  Exit:       n/a
  1914. ;  Calls:      none
  1915. ;  Changes:    none
  1916. ;--------------------------------------------------------------------------;
  1917. PROC setvect
  1918.  
  1919.    push     ax
  1920.    mov      ah, 25h                       ; set address of handler
  1921.    int      DOS
  1922.    pop      ax
  1923.    ret
  1924. ENDP setvect
  1925.  
  1926.  
  1927. EVEN
  1928. ;--------------------------------------------------------------------------;
  1929. ;  Purpose:    Finds the next in a chain of ISRs.
  1930. ;  Notes:      ISRs must be shared according to the IBM Interrupt
  1931. ;                 Sharing Protocol.
  1932. ;  Requires:   8086-class CPU.
  1933. ;  Entry:      ES:BX = entry point for a given ISR.
  1934. ;  Exit:       ES:BX = entry point for next ISR in the chain,
  1935. ;              cf = 1 on error
  1936. ;  Calls:      none
  1937. ;  Changes:    BX, ES, cf
  1938. ;--------------------------------------------------------------------------;
  1939. PROC find_NextISR
  1940.  
  1941. ; Save DS, then set it to ES. This will avoid segment overrides below.
  1942.    push     ds es
  1943.    pop      ds
  1944.  
  1945. ; Run three tests to see if the ISR obeys the protocol.
  1946. ;1) Entry should be a short jump (opcode 0EBh).
  1947. ;2) Sig should equal a special value ("KB").
  1948. ;3) Reset should be another short jump.
  1949.    cmp      [BYTE PTR (ISR PTR bx).Entry], 0ebh
  1950.    jnz      SHORT @@Abort
  1951.    cmp      [(ISR PTR bx).Sig], TSRMAGIC
  1952.    jnz      SHORT @@Abort
  1953.    cmp      [BYTE PTR (ISR PTR bx).Reset], 0ebh
  1954.    jnz      SHORT @@Abort
  1955.  
  1956. ; Ok, looks like the ISR is following the Interrupt Sharing Protocol.
  1957. ; nb: cf will be clear as a result of the last comparison.
  1958.    les      bx, [(ISR PTR bx).OldISR]
  1959.    jmp      SHORT @@Fin
  1960.  
  1961. ; Uh, oh, somebody's not being very cooperative or we've hit DOS/BIOS.
  1962. @@Abort:
  1963.    stc                                    ; flag error
  1964.  
  1965. @@Fin:
  1966.    pop      ds
  1967.    ret
  1968.  
  1969. ENDP find_NextISR
  1970.  
  1971.  
  1972. ;--------------------------------------------------------------------------;
  1973. ;  Purpose:    Finds the previous in a chain of ISRs.
  1974. ;  Notes:      ISRs must be shared according to the IBM Interrupt
  1975. ;                 Sharing Protocol.
  1976. ;  Requires:   8086-class CPU and DOS v2.0 or better.
  1977. ;  Entry:      AL = vector hooked,
  1978. ;              ES:BX = entry point for a given ISR.
  1979. ;  Exit:       ES:BX = entry point for next ISR in the chain,
  1980. ;              cf = 1 on error
  1981. ;  Calls:      getvect, find_NextISR
  1982. ;  Changes:    BX, ES, cf
  1983. ;--------------------------------------------------------------------------;
  1984. PROC find_PrevISR
  1985.  
  1986.    push     ax cx dx
  1987.  
  1988. ; Stack holds previous ISR. Initialize it to a null pointer.
  1989.    ZERO     cx
  1990.    push     cx cx
  1991.  
  1992. ; Point CX:DX to current ISR, then get first ISR in the chain.
  1993.    mov      cx, es
  1994.    mov      dx, bx
  1995.    call     getvect                       ; AL -> ES:BX
  1996.    jmp      SHORT @@Cmp
  1997.  
  1998. ; Cycle through ISRs until either a match is found or we can't go further.
  1999. @@Next:
  2000.    add      sp, 4                         ; get rid of two words on stack
  2001.    push     es bx                         ; now save ES:BX
  2002.    call     find_NextISR                  ; ES:BX -> ES:BX
  2003.    jc       SHORT @@Fin                   ; abort on error
  2004. @@Cmp:
  2005.    mov      ax, es                        ; are segs the same?
  2006.    cmp      ax, cx
  2007.    jnz      SHORT @@Next
  2008.    cmp      dx, bx                        ; what about offsets?
  2009.    jnz      SHORT @@Next
  2010.  
  2011. @@Fin:
  2012.    pop      bx es                         ; pointer to previous ISR
  2013.    pop      dx cx ax
  2014.    ret
  2015.  
  2016. ENDP find_PrevISR
  2017.  
  2018.  
  2019. ;--------------------------------------------------------------------------;
  2020. ;  Purpose:    Hooks into an ISR and keeps track of previous ISR.
  2021. ;  Notes:      none
  2022. ;  Requires:   8086-class CPU and DOS v2.0 or better.
  2023. ;  Entry:      AL = vector to hook,
  2024. ;              ES:BX = pointer to a structure of type ISR.
  2025. ;  Exit:       n/a
  2026. ;  Calls:      getvect, setvect
  2027. ;  Changes:    n/a
  2028. ;--------------------------------------------------------------------------;
  2029. PROC hook_ISR
  2030.  
  2031.    push     bx dx bp ds es
  2032.  
  2033. ; Save old vector to it can be restored later. Then set new hook.
  2034.    push     es bx                         ; need them later
  2035.    call     getvect                       ; AL -> ES:BX
  2036.    pop      dx ds                         ; recover pointer to ISR
  2037.    mov      bp, dx                        ; use BP for indexing
  2038.    mov      [WORD (ISR PTR bp).OldISR], bx
  2039.    mov      [WORD ((ISR PTR bp).OldISR)+2], es
  2040.    call     setvect                       ; uses DS:DX
  2041.  
  2042.    pop      es ds bp dx bx
  2043.    ret
  2044.  
  2045. ENDP hook_ISR
  2046.  
  2047.  
  2048. ;--------------------------------------------------------------------------;
  2049. ;  Purpose:    Unhooks an ISR if possible.
  2050. ;  Notes:      Unhooking an ISR is more complicated than hooking one
  2051. ;                 because of the need to support interrupt sharing.
  2052. ;  Requires:   8086-class CPU and DOS v2.0 or better.
  2053. ;  Entry:      AL = vector hooked,
  2054. ;              ES:BX = entry point of current ISR.
  2055. ;  Exit:       cf = 1 on error
  2056. ;  Calls:      find_PrevISR, setvect
  2057. ;  Changes:    cf
  2058. ;--------------------------------------------------------------------------;
  2059. PROC unhook_ISR
  2060.  
  2061.    push     bx cx dx ds es
  2062.  
  2063. ; Point DS:DX to next ISR, then ES:BX to previous ISR in the chain.
  2064.    lds      dx, [(ISR PTR es:bx).OldISR]
  2065.    call     find_PrevISR                  ; ES:BX -> ES:BX
  2066.    jc       SHORT @@Fin                   ; abort on error
  2067.  
  2068. ; If find_PrevISR() returned a null pointer, then the current ISR
  2069. ; is first in the chain; just use DOS to reassign the vector.
  2070. ; Otherwise, update the OldISR entry in the previous handler.
  2071.    mov      cx, es                        ; did find_PrevISR() ...
  2072.    or       cx, bx                        ; return null pointer?
  2073.    jnz      SHORT @@Update                ;   no. update OldISR
  2074.    call     setvect                       ;   yes, hook AL to DS:DX
  2075.    jmp      SHORT @@Fin
  2076. @@Update:
  2077.    mov      [WORD (ISR PTR es:bx).OldISR], dx
  2078.    mov      [WORD ((ISR PTR es:bx).OldISR)+2], ds
  2079.  
  2080. @@Fin:
  2081.    pop      es ds dx cx bx
  2082.    ret
  2083.  
  2084. ENDP unhook_ISR
  2085.  
  2086.  
  2087. EVEN
  2088. AMI         equ      2dh                  ; Alternate Multiplex Interrupt
  2089. ENVBLK      equ      2ch                  ; ptr in PSP to environment block
  2090.  
  2091.  
  2092. ;--------------------------------------------------------------------------;
  2093. ;  Purpose:    Frees up a program's environment block.
  2094. ;  Notes:      Programs such as PMAP or MEM scan environment blocks to
  2095. ;                 learn names of TSRs. Freeing it means such programs
  2096. ;                 will not be able to identify the TSR.
  2097. ;              It's ASSUMED the ENV BLOCK has NOT ALREADY been FREED.
  2098. ;  Requires:   8086-class CPU and DOS v2.0 or better.
  2099. ;  Entry:      ES = segment of program's PSP.
  2100. ;  Exit:       none
  2101. ;  Calls:      none
  2102. ;  Changes:    none
  2103. ;--------------------------------------------------------------------------;
  2104. PROC free_Env
  2105.  
  2106.    push     ax ds es
  2107.  
  2108.    push     es                            ; point DS to PSP too
  2109.    pop      ds
  2110.    mov      es, [ENVBLK]                  ; pointer to env block
  2111.    mov      ah, 49h                       ; free memory block
  2112.    int      DOS
  2113.    mov      [WORD PTR ENVBLK], 0          ; make it 0
  2114.  
  2115.    pop      es ds ax
  2116.    ret
  2117.  
  2118. ENDP free_Env
  2119.  
  2120.  
  2121. ;--------------------------------------------------------------------------;
  2122. ;  Purpose:    Replaces a program's real environment with a smaller, fake
  2123. ;                 one to save space. Programs like PMAP and MEM though
  2124. ;                 will still be able to identify TSRs.
  2125. ;  Notes:      If run with DOS version lower than v3.10, the environment
  2126. ;                 block is merely freed.
  2127. ;  Requires:   8086-class CPU and DOS v2.0 or better.
  2128. ;  Entry:      CX = size in bytes of pseudo-environment block (if 0 
  2129. ;                 one is created containing just program name/args),
  2130. ;              DS:SI = pointer to pseudo-environment block,
  2131. ;              ES = segment of program's PSP.
  2132. ;  Exit:       none
  2133. ;  Calls:      getvdos, free_Env, strlen
  2134. ;  Changes:    none
  2135. ;--------------------------------------------------------------------------;
  2136. PROC fake_Env
  2137.  
  2138.    push     ax bx cx di si ds es
  2139.    pushf
  2140.  
  2141. ; Make sure DOS is v3.10 or better. If not, just free environment.
  2142. ; nb: I could code this to handle old versions so long as caller
  2143. ; supplies a real block, but why bother?
  2144.    call     getvdos                       ; get DOS version
  2145.    xchg     al, ah
  2146.    cmp      ax, (3 SHL 8) + 10            ; v3.10 or better?
  2147.    jae      SHORT @@FindEnv               ;   yes
  2148.    call     free_Env                      ;   no, just free it
  2149.    jmp      SHORT @@Fin
  2150.  
  2151. ; Locate environment block.
  2152. @@FindEnv:
  2153.    mov      bx, [es:ENVBLK]               ; pointer to env block
  2154.    mov      es, bx
  2155.  
  2156. ; If CX is zero, point DS:SI to just the program name/args in the
  2157. ; current environment. This format was introducted with DOS v3.10.
  2158. ;
  2159. ; nb: Refer to _Undocumented DOS, p 399 for format of environment block.
  2160.    cld                                    ; scasb and movsb must go forward
  2161.    or       cx, cx                        ; is CX zero?
  2162.    jnz      SHORT @@GetMem                ;   no
  2163.    mov      ds, bx                        ; point DS to env block too
  2164.    ZERO     al                            ; ends of ASCIIz strings
  2165.    ZERO     di                            ; start at offset 0
  2166. @@NextString:
  2167.    call     strlen                        ; find length of string at ES:DI
  2168.    add      di, cx                        ; update DI
  2169.    inc      di                            ;   and past EOS
  2170.    scasb                                  ; are we at another 0?
  2171.    jne      SHORT @@NextString            ;   no
  2172.    mov      si, di                        ; point SI to
  2173.    dec      si                            ;   EOS in ...
  2174.    dec      si                            ;   last string
  2175.    mov      [WORD PTR es:di], 1           ; only want prog name/args
  2176.    inc      di                            ; point to start of string
  2177.    inc      di
  2178.    call     strlen                        ; find its length
  2179.    add      cx, di                        ; get # bytes to move
  2180.    sub      cx, si
  2181.    inc      cx
  2182.  
  2183. ; At this point, CX holds number of bytes to allocate and DS:SI point
  2184. ; to a copy of the pseudo-environment block.
  2185. @@GetMem:
  2186.    ZERO     di                            ; either way, destination = 0
  2187.    mov      bx, cx                        ; from # bytes
  2188.    REPT     4
  2189.       shr      bx, 1                      ; get # paragraphs
  2190.    ENDM
  2191.    inc      bx                            ; think what if CX < 0fh
  2192.    push     bx                            ; must save BX if DOS fails
  2193.    mov      ah, 48h                       ; allocate memory
  2194.    int      DOS                           ; returns block in AX
  2195.    pop      bx
  2196.    jc       SHORT @@JustResize            ; cf => failure
  2197.  
  2198. ; Memory allocation succeeded so: (1) Copy to new block. (2) Adjust
  2199. ; pointer in program's PSP. (3) Free old block.
  2200.    push     es                            ; points to old env block
  2201.    mov      es, ax                        ; new block
  2202.    rep      movsb
  2203.    mov      ah, 62h                       ; get program's PSP
  2204.    int      DOS                           ; returns it in BX
  2205.    mov      ds, bx
  2206.    mov      [ENVBLK], es                  ; pointer to new env block
  2207.    pop      es                            ; recover pointer to old env
  2208.    mov      ah, 49h                       ; free it
  2209.    int      DOS
  2210.    jmp      SHORT @@Fin
  2211.  
  2212. ; Memory allocation failed so we'll use existing block and resize it.
  2213. @@JustResize:
  2214.    rep      movsb
  2215.    mov      ah, 4ah                       ; modify allocation
  2216.    int      DOS
  2217.  
  2218. @@Fin:
  2219.    popf
  2220.    pop      es ds si di cx bx ax
  2221.    ret
  2222.  
  2223. ENDP fake_Env
  2224.  
  2225.  
  2226. ;--------------------------------------------------------------------------;
  2227. ;  Purpose:    Checks if a TSR has been installed in memory.
  2228. ;  Notes:      For a description of the steps followed here, see Ralf
  2229. ;                 Brown's alternate multiplex proposal.
  2230. ;              This procedure MUST BE RUN before going resident and
  2231. ;                 the multiplex id returned SHOULD BE SAVED in the
  2232. ;                 resident data area.
  2233. ;  Requires:   8086-class CPU
  2234. ;  Entry:      DS:SI = pointer to TSR's signature string.
  2235. ;  Exit:       AL = 0 if not installed, = 1 if installed, = 2 if all
  2236. ;                 multiplex ids are in use,
  2237. ;              AH = multiplex id to use based on AL,
  2238. ;              CX = TSR version number if installed,
  2239. ;              DX:DI = pointer to resident copy of TSR's sig if AL = 1.
  2240. ;  Calls:      getvect, memcmp
  2241. ;  Changes:    AX, CX, DX, DI
  2242. ;--------------------------------------------------------------------------;
  2243. PROC check_ifInstalled
  2244.  
  2245.    push     bx es
  2246.  
  2247. ; Do a quick check to see if 2d is hooked. 
  2248.    mov      al, AMI                       ; alternate multiplex interrupt
  2249.    call     getvect                       ; handler address in ES:BX
  2250.    ZERO     ax
  2251.    cmp      [BYTE PTR es:bx], 0cfh        ; is it IRET opcode?
  2252.    jz       SHORT @@Fin                   ;   yes, return with AX = 0
  2253.  
  2254. ; Do an install check on each possible multiplex id. 
  2255.    ZERO     bx                            ; marks 1st unused mplex id
  2256. @@CheckIt:
  2257.    ZERO     al                            ; be sure to do install check
  2258.    int      AMI                           ; might trash CX and DX:DI
  2259.    or       al, al                        ; is AL zero still?
  2260.    jnz      SHORT @@CmpSigs               ;   no, multiplex's in use
  2261.  
  2262. ; It's not in use. Save if it's the first.
  2263.    or       bl, bl                        ; 1st available id found already?
  2264.    jnz      SHORT @@NextMPlex             ;   yes
  2265.    inc      bl                            ;   no, but flag it now
  2266.    mov      bh, ah                        ;     and hold onto mplex
  2267.    jmp      SHORT @@NextMPlex
  2268.  
  2269. ; Compare first 16 bytes of sigs. DS:SI points to a known sig;
  2270. ; DX:DI to one somewhere in resident code.
  2271. @@CmpSigs:
  2272.    push     cx                            ; save TSR version number
  2273.    mov      cx, 16                        ; # bytes in sigs to compare
  2274.    mov      es, dx                        ; memcmp() needs ES:DI and DS:SI
  2275.    call     memcmp
  2276.    pop      cx                            ; recover TSR version number
  2277.    jnz      SHORT @@NextMPlex
  2278.    mov      al, 1
  2279.    jmp      SHORT @@Fin
  2280.  
  2281. ; Move on to next multiplex number.
  2282. @@NextMPlex:
  2283.    add      ah, 1                         ; sets zf if AH was 255. Done?
  2284.    jnz      SHORT @@CheckIt               ;   no, back for more
  2285.    mov      ah, bh                        ;   yes, AH = 1st available id
  2286.    or       bl, bl                        ;   did we run out?
  2287.    jnz      SHORT @@Fin                   ;     no
  2288.    mov      al, 2                         ;     yes
  2289.  
  2290. @@Fin:
  2291.    pop      es bx
  2292.    ret
  2293.  
  2294. ENDP check_ifInstalled
  2295.  
  2296.  
  2297. ;--------------------------------------------------------------------------;
  2298. ;  Purpose:    Installs a TSR in memory.
  2299. ;  Notes:      This procedure never returns.
  2300. ;              No changes are made here to the environment block.
  2301. ;              Entry points are assumed relative to ES.
  2302. ;              Call check_ifInstalled() to determine which multiplex
  2303. ;                 id will be used.
  2304. ;  Requires:   8086-class CPU and DOS v2.0 or better.
  2305. ;  Entry:      DX = number of paragraphs to reserve,
  2306. ;              ES:BX = pointer to a structure of ISRHOOK.
  2307. ;  Exit:       n/a
  2308. ;  Calls:      hook_ISR
  2309. ;  Changes:    n/a
  2310. ;--------------------------------------------------------------------------;
  2311. PROC install_TSR
  2312.  
  2313. ; Set hooks as specified by ISRHOOK structure. 
  2314.    mov      bp, bx                        ; BX needed when hooking ISRs
  2315. @@NextHook:
  2316.    mov      al, [(ISRHOOK PTR bp).Vector]
  2317.    mov      bx, [(ISRHOOK PTR bp).Entry]
  2318.    call     hook_ISR                      ; AL, ES:BX -> n/a
  2319.    add      bp, SIZE ISRHOOK
  2320.    cmp      al, AMI                       ; at end of table?
  2321.    jnz      SHORT @@NextHook              ;   no
  2322.  
  2323. ; And now go resident. Note that DX already holds # paragraphs to keep.
  2324.    mov      ax, 3100h                     ; terminate/stay resident, rc = 0
  2325.    int      DOS                           ; via DOS
  2326.    ret                                    ; ***never reached***
  2327.  
  2328. ENDP install_TSR
  2329.  
  2330.  
  2331. ;--------------------------------------------------------------------------;
  2332. ;  Purpose:    Removes a TSR if possible.
  2333. ;  Notes:      Caller should use check_ifInstalled() to make sure the
  2334. ;                 TSR has first been installed.
  2335. ;              Entry points are assumed to be relative to ES.
  2336. ;  Requires:   8086-class CPU and DOS v2.0 or better.
  2337. ;  Entry:      ES:BX = pointer to a structure of ISRHOOK.
  2338. ;  Exit:       cf set if operation failed
  2339. ;  Calls:      find_PrevISR, unhook_ISR
  2340. ;  Changes:    AX, cf
  2341. ;--------------------------------------------------------------------------;
  2342. PROC remove_TSR
  2343.  
  2344.    push     bx dx bp ds es                ; save registers
  2345.  
  2346. ; Set DS to ES to avoid segment overrides. Also, use BP for indexing into 
  2347. ; the hook table, and save it in DX as it's needed later.
  2348.    push     es
  2349.    pop      ds
  2350.    mov      bp, bx
  2351.    mov      dx, bx
  2352.  
  2353. ; For each vector in the hook table, make sure the ISR can be unhooked.
  2354. @@NextVect:
  2355.    mov      al, [(ISRHOOK PTR bp).Vector]
  2356.    mov      bx, [(ISRHOOK PTR bp).Entry]
  2357.    push     es                            ; hang onto this
  2358.    call     find_PrevISR                  ; able to find it?
  2359.    pop      es
  2360.    jc       SHORT @@Fin                   ;   no, abort
  2361.    add      bp, SIZE ISRHOOK
  2362.    cmp      al, AMI                       ; at end of table?
  2363.    jnz      SHORT @@NextVect              ;   no
  2364.  
  2365. ; It's possible to unhook all vectors, so go to it. 
  2366.    mov      bp, dx
  2367. @@NextHook:
  2368.    mov      al, [(ISRHOOK PTR bp).Vector]
  2369.    mov      bx, [(ISRHOOK PTR bp).Entry]
  2370.    call     unhook_ISR                    ; AL, ES:BX -> n/a
  2371.    jc       SHORT @@Fin                   ; it had better succeed!
  2372.    add      bp, SIZE ISRHOOK
  2373.    cmp      al, AMI                       ; at end of table?
  2374.    jnz      SHORT @@NextHook              ;   no
  2375.  
  2376. ; Now free TSR's memory.
  2377.    mov      bx, [ENVBLK]
  2378.    or       bx, bx                        ; any environment block?
  2379.    jz       SHORT @@MainMem               ;   no
  2380.    mov      es, bx                        ;   yes, free it
  2381.    mov      ah, 49h
  2382.    int      DOS                           ; trashes AH
  2383.    jc       SHORT @@Fin                   ; shouldn't be necessary
  2384. @@MainMem:
  2385.    mov      ah, 49h
  2386.    mov      bx, ds                        ; free TSR's memory
  2387.    mov      es, bx
  2388.    int      DOS
  2389.  
  2390. @@Fin:
  2391.    pop      es ds bp dx bx                ; pop registers
  2392.    ret
  2393.  
  2394. ENDP remove_TSR
  2395.  
  2396.  
  2397. EVEN
  2398. ;-------------------------------------------------------------------------;
  2399. ;  Purpose:    Converts character to lowercase.
  2400. ;  Notes:      none
  2401. ;  Requires:   8086-class CPU.
  2402. ;  Entry:      AL = character to be converted.
  2403. ;  Exit:       AL = converted character.
  2404. ;  Calls:      none
  2405. ;  Changes:    AL
  2406. ;              flags
  2407. ;-------------------------------------------------------------------------;
  2408. PROC tolower
  2409.  
  2410.    cmp      al, 'A'                       ; if < 'A' then done
  2411.    jb       SHORT @@Fin
  2412.    cmp      al, 'Z'                       ; if > 'Z' then done
  2413.    ja       SHORT @@Fin
  2414.    or       al, 20h                       ; make it lowercase
  2415. @@Fin:
  2416.    ret
  2417.  
  2418. ENDP tolower
  2419.  
  2420.  
  2421. ;-------------------------------------------------------------------------;
  2422. ;  Purpose:    Converts character to uppercase.
  2423. ;  Notes:      none
  2424. ;  Requires:   8086-class CPU.
  2425. ;  Entry:      AL = character to be converted.
  2426. ;  Exit:       AL = converted character.
  2427. ;  Calls:      none
  2428. ;  Changes:    AL
  2429. ;              flags
  2430. ;-------------------------------------------------------------------------;
  2431. PROC toupper
  2432.  
  2433.    cmp      al, 'a'                       ; if < 'a' then done
  2434.    jb       SHORT @@Fin
  2435.    cmp      al, 'z'                       ; if > 'z' then done
  2436.    ja       SHORT @@Fin
  2437.    and      al, not 20h                   ; make it uppercase
  2438. @@Fin:
  2439.    ret
  2440.  
  2441. ENDP toupper
  2442.  
  2443.  
  2444. EVEN
  2445. ;--------------------------------------------------------------------------;
  2446. ;  Purpose:    Compares two regions of memory.
  2447. ;  Notes:      none
  2448. ;  Requires:   8086-class CPU.
  2449. ;  Entry:      CX = number of bytes to compare,
  2450. ;              DS:SI = start of 1st region of memory,
  2451. ;              ES:DI = start of 2nd region.
  2452. ;  Exit:       zf = 1 if equal.
  2453. ;  Calls:      none
  2454. ;  Changes:    zf
  2455. ;--------------------------------------------------------------------------;
  2456. PROC memcmp
  2457.  
  2458.    push     cx di si
  2459.    pushf                                  ; save direction flag
  2460.    cld
  2461.    repe     cmpsb                         ; compare both areas
  2462.    popf                                   ; recover direction flag
  2463.    dec      di
  2464.    dec      si
  2465.    cmpsb                                  ; set flags based on final byte
  2466.    pop      si di cx
  2467.    ret
  2468.  
  2469. ENDP memcmp
  2470.  
  2471.  
  2472. EVEN
  2473. ;-------------------------------------------------------------------------;
  2474. ;  Purpose:    Calculates length of an ASCIIZ string.
  2475. ;  Notes:      Terminal char is _not_ included in the count.
  2476. ;  Requires:   8086-class CPU.
  2477. ;  Entry:      ES:DI = pointer to string.
  2478. ;  Exit:       CX = length of string,
  2479. ;              cf = 0 and zf = 1 if EOS found,
  2480. ;              cf = 1 and zf = 0 if EOS not found within segment.
  2481. ;  Calls:      none
  2482. ;  Changes:    CX,
  2483. ;              flags
  2484. ;-------------------------------------------------------------------------;
  2485. PROC strlen
  2486.  
  2487.    push     ax di
  2488.    pushf
  2489.    cld                                    ; scan forward only
  2490.    mov      al, EOS                       ; character to search for
  2491.    mov      cx, di                        ; where are we now
  2492.    not      cx                            ; what's left in segment - 1
  2493.    push     cx                            ; save char count
  2494.    repne    scasb
  2495.    je       SHORT @@Done
  2496.    scasb                                  ; test final char
  2497.    dec      cx                            ; avoids trouble with "not" below
  2498.  
  2499. @@Done:
  2500.    pop      ax                            ; get original count
  2501.    sub      cx, ax                        ; subtract current count
  2502.    not      cx                            ; and invert it
  2503.    popf                                   ; restore df
  2504.    dec      di
  2505.    cmp      [BYTE PTR es:di], EOS
  2506.    je       SHORT @@Fin                   ; cf = 0 if equal
  2507.    stc                                    ; set cf => error
  2508.  
  2509. @@Fin:
  2510.    pop      di ax
  2511.    ret
  2512.  
  2513. ENDP strlen
  2514.  
  2515.  
  2516. END
  2517.